演员:基于线程与事件驱动

Actors: Thread based vs Event driven

阅读有关基于 actor 的编程时,我遇到了基于线程的 actor 和事件驱动的 actor。

两者有什么区别?什么时候使用哪个?

Actor 模型本身就是一个并发规范。从 Actor 模型的角度来看,没有 "events"、"threads" 或 "processes" 的概念,只有 Actors 和消息。

这实际上是 Actor 模型的优势之一,因为它采用不同的并发方法来提供更好的隔离。任何共享信息都必须通过消息进行交换,并且特定消息的处理是原子的,因此在执行 Actor 的消息处理程序期间不需要特殊的并发同步技术。

相比之下,线程 运行 在共享内存 space 中,这需要仔细协调内存更新(通过互斥锁和锁)或模拟信息交换(通过队列...使用锁)或互斥体)。进程实现了一个与 Actors 更相似的并发模型,因为它们提供了更多的隔离,但没有对消息交换的原生支持,因此必须经常通过管道或套接字以及相应的协议和资源管理在进程内构建和管理信息交换; Actor 照原样提供此消息交换功能 "out of the box"。

实际上,大多数操作系统提供 "process" 和 "thread" 并发实现而不是 Actors,因此您使用的 Actor 框架需要将 Actors 映射到操作系统的底层并发模型。一些通过将每个 Actor 实现为单独的线程来实现,一些通过将 Actors 实现为单独的进程,一些通过使用一个或多个线程并遍历一组 Actors 来传递消息。即使是事件驱动的实现,如 Beam(Erlang VM),如果他们希望利用多个处理器上下文并且在 VM 和硬件之间有一个操作系统,也需要执行此映射。

当您编写 Actors 时,您是在写入并发抽象,不应关心由您的 Actor 框架处理的任何并发模型重新映射:这是框架的目的和范围。

不幸的是,由于 OS 管理系统资源,并且 OS 的并发模型(以及您的语言)通常围绕线程 and/or 进程模型构建,使用系统资源时,您需要考虑映射的影响。例如,如果您的 Actor 是用 C 编写的,并且将在文件或套接字上进行阻塞 read() 系统调用,则事件驱动模型将导致 all Actor 停止,而线程- 或基于进程的 Actor 模型应该允许其他 Actor 继续处理,而另一个 Actor 在 read() 调用时被阻塞。我也是这种情况,但使用 CPython(例如),由于 CPython GIL,事件驱动和线程化 Actor 模型仍会阻塞所有其他 Actor,但基于过程的 Python Actor 模型仍然能够允许并发执行 Actor。许多 Actor 框架将提供额外的功能来帮助在使用系统资源时维护 Actor 模型抽象,因此在编写与 OS 管理的资源交互的 Actors 时,您需要检查框架的详细信息(尤其是以阻塞方式) ).

一些 Actor 框架能够根据启动配置参数更改实现模式(例如 运行ning 为事件驱动、线程或基于进程,具体取决于启动参数)。这可能非常有用;例如,使用协作、单线程实现的简单测试允许可重复和可理解的测试结果,而生产应用程序仍然 运行s 具有完全并发且没有修改。

一般来说,在编写 Actors 时,您应该尝试使用基于 Actor 的并发实践,并将可能受底层实现影响的任何外部接口迁移到设计的边缘,以便在需要时轻松重新设计。