何时在Scala中使用Futures比Actors更合适(或反之亦然)?

9
假设我需要运行几个并发任务。
我可以将每个任务包装在一个Future中,并等待它们的完成。或者,我可以为每个任务创建一个Actor。每个Actor将执行其任务(例如在收到“start”消息时)并发送结果回来。
我想知道何时应该使用前者(使用Future)和后者(使用Actor)方法以及为什么Future方法被认为更适用于上述情况。
3个回答

8

因为它的语法更简单。

val tasks: Seq[() => T] = ???
val futures = tasks map {
  t => future { t() }
}
val results: Future[Seq[T]] = Future.sequence(futures)

你可以等待使用 Await.result 获得结果,也可以进一步映射/在 for-循环中使用它或在其上安装回调函数。

相比之下,实例化所有演员、向它们发送消息、编写它们的 receive 代码块、从它们接收响应并关闭它们,通常需要更多的样板代码。


2
区别也在于底层实现,但是Actor模型通常是一种更强大和表达力更强的编程模型——你可能可以使用actors创建类似于Futures API的API。使用futures实现actors会更加繁琐。 - axel22
好的,我看到了这些模型之间的区别。你能解释一下实现方案之间的区别吗? - Michael
2
期货API通常通过创建分支/加入任务并将其推入分支/加入池来工作,然后将任务分配给其中一个工作线程。有时最后执行计划执行的任务时,会通过单个未来批量执行它来进行优化(上次我检查时是这样的)。演员实现还依赖于线程池,并从任务中处理存储在演员邮箱中的消息,但我对其内部不够了解,无法告诉你更多。 - axel22
非常感谢。有趣的是哪些任务更适合actor模型(或future模型)。我会考虑一下。 - Michael

6
作为一般规则,使用最简单的并发模型来适应您的应用程序,而不是最强大的。按照从简单到复杂的顺序排序将是:顺序编程->并行集合->futures->无状态actors->有状态actors->带软件事务内存的线程->带显式锁定的线程->带无锁算法的线程。选择此列表中解决您问题的第一个。越往下走,风险和复杂性就越大,因此您最好以概念强度换取简单性。

你从哪里得到这个序列的?对我来说,它看起来不一致,因为使用 futures 意味着需要显式锁定线程。而且,有哪些使用阻塞队列的隐式锁定线程呢?此外,你的 actor 可以有多少个输入?如果只有一个(如 Scala 和 Akka actors),那么你应该单独列出多输入的 actors。如果你所指的 actors 可以有多个输入,那么无状态和有状态的 actors 之间没有区别(状态是传递给额外输入的令牌)。 - Alexei Kaigorodov
1
该列表只是个人意见。您的更改是合理的,除了 futures 绝不意味着线程和显式锁定。无副作用的 futures 有效地建模数据流变量,并在不允许非确定性的情况下获得并发性。 - Dave Griffith
Java中的Futures(java.util.concurrent.Future)以及大多数其他语言中的Futures(请参见http://en.wikipedia.org/wiki/Futures_and_promises)都是阻塞的,因此暗示着线程。你所说的Futures是指哪些? - Alexei Kaigorodov
1
实现期货确实意味着在底层使用线程,就像几乎任何并发构造一样。尽管如此,我会断言它是一个比一般线程更简单的模型,因为它(在没有副作用的情况下)是确定性的,并且能够支持本地推理。并行集合和无状态演员具有类似的特征。请注意,这些模型实际上比带锁的线程要弱。有些并发程序你不能使用无副作用的期货创建,包括但不限于那些存在竞争条件的程序。 - Dave Griffith
2
我认为将futures视为阻塞队列的一种特殊情况,并将您对futures的考虑应用于所有阻塞队列。无论如何,我喜欢这个讨论,但我担心它已经偏离了主题。如果您想在其他地方继续讨论,请告诉我(我的电子邮件地址在我的个人资料中)。 - Alexei Kaigorodov

1
我倾向于认为当你有交互线程时,演员是有用的。在你的情况下,似乎所有的工作都是独立的;我会使用 futures。

2
阅读其他答案后,我认为新的答案应该更加详细一些。 - manuell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接