在Akka和Java中放弃线程/CPU直到异步调用完成



我正在寻找与Python的yield from或gevent的monkey补丁等效的Java/Akka。


更新

通信中对问题的内容有一些困惑,所以让我重申一下这个问题:

如果我有未来,我如何在不阻塞线程和不返回调用方的情况下等待未来竞争,直到未来完成


假设我们有阻止的方法

public Object foo() {
Object result = someBlockingCall();
return doSomeThingWithResult(result);
}

为了实现异步,我们将向SomeBlockingCall()传递一个回调:

public void foo() {
someAsyncCall(new Handler() {
public void onSuccess(Object result) {
message = doSomethingWithResult(result);
callerRef.tell(message, ActorRef.noSender());
}
});
}

foo()的调用现在在结果准备好之前返回,因此调用者不再获得结果。我们必须通过传递消息将结果返回给调用者。要将同步代码转换为异步Akka代码,需要重新设计调用者。

我想要的是异步代码,它看起来像Python的Gevent这样的同步代码。

我想写:

public Object foo() {
Future future = someAsyncCall();
// save stack frame, go back to the event loop and relinquish CPU
// so other events can use the thread,
// and come back when future is complete and restore stack frame
return yield(future);
}

这将允许我在不重新设计的情况下使同步代码异步化。

这可能吗?

注意:Play框架似乎用async()AsyncResult伪造了这一点。但这通常不会起作用,因为我必须编写处理AsyncResult的代码,它看起来像上面的回调处理程序。

我认为尝试恢复一个更直接的同步设计,尽管这是一个高效的设计,但实际上是一个很好的意图和想法(例如,请参阅此处)。

Quasar具有从异步API中获得仍然高效的同步/阻塞API的功能(请参阅本博客文章),这正是您想要的。

根本问题是而不是同步/阻塞样式本身是糟糕的(实际上异步和同步是双重样式,可以相互转换,例如参见此处),但阻塞Java的重量级线程并不是有效的:这不是抽象问题,而是实现问题,因此,与其仅仅因为实现效率低下而放弃更简单的线程抽象,我同意,对于代码的未来,最好尝试寻找更高效的线程实现。

正如Roland所暗示的,Quasar在JVM中添加了轻量级线程或光纤,因此您可以在不放弃线程抽象和语言中可用的常规命令式控制流结构(序列、循环等)的情况下获得异步框架的相同性能。

它还将JVM/JDK的线程及其光纤统一在一个公共的strand接口下,使它们可以无缝互操作,并提供了java.util.concurrent到这个统一概念的移植。

在线程(光纤或常规线程)之上,Quasar还提供成熟的Erlang风格的actor阻塞Go类通道数据流编程,因此您可以选择最适合您的技能和需求的并发编程范式,而无需强制执行。

它还为流行和标准技术提供了绑定(作为Comsat项目的一部分),因此您可以保留您的代码资产,因为移植工作将是最小的(如果有的话)。出于同样的原因,如果您选择退出,您也可以轻松退出

目前,Quasar已经绑定了Java7和8,Pulsar项目下的Clojure以及JetBrain的Kotlin。Quasar基于JVM字节码检测,如果存在集成模块,它可以真正与任何JVM语言一起工作,并且它提供了构建其他模块的工具。

你的问题的答案是"否",这在很大程度上是故意的。将方法写为异步意味着返回Future作为其结果,方法本身将不执行计算,而是安排稍后提供结果。然后,您可以将这个Future传递到正确的位置,以便进一步使用它,例如,通过使用许多组合子之一(如maprecover等)对其进行转换

无论使用哪种技术,等待"未来"的严格结果都必须阻止当前的执行线程。使用普通JVM线程,您将阻止操作系统中的真实线程,使用Quasar将阻止Fiber,使用Akka将阻止Actor(*);封锁意味着封锁,没有办法绕过它。


(*)在Actor中,您将在稍后通过消息获得结果,在此之前,您必须切换行为,以便根据您的用例隐藏、拒绝或丢弃新的传入消息。

最新更新