这是一个非常重要且经典的并发计算模型,旨在简化分布式和并行系统的开发。它的核心思想是“万物皆Actor”,通过消息传递进行通信。
1. 核心思想:应对并发编程的挑战
传统的基于共享内存的并发编程(如使用锁和线程)非常复杂且容易出错,常见问题包括:
竞态条件:结果依赖于线程执行的时序。
死锁:多个线程互相等待对方释放锁。
难以扩展:管理和同步大量线程极其困难。
Actor 模型通过一套不同的原则来解决这些问题。
2. 什么是 Actor?
一个 Actor 是计算的基本单元,你可以把它想象成一个拥有独立状态和行为的小型处理单元。每个 Actor 都有唯一一个地址(邮箱地址),其他人只能通过向这个地址发送消息来与之交互。
一个 Actor 包含三个核心部分:
状态:Actor 可以拥有自己的私有数据(状态)。关键点:这个状态是完全私有的,不能被其他任何 Actor 直接访问或修改。这从根本上避免了竞态条件,因为不存在共享内存。
行为:Actor 包含了一系列逻辑,定义了它如何响应接收到的消息。这些逻辑决定了它如何处理消息、如何改变自己的状态、以及要向谁发送新消息。
邮箱:每个 Actor 都有一个消息队列(邮箱)。发送给它的所有消息都会先进入这个队列,按照先进先出的顺序等待处理。邮箱解耦了消息发送者和接收者,使得系统在负载高峰时也能保持稳定。
3. Actor 模型的核心规则
消息传递是唯一的交互方式:Actor 之间不能直接调用对方的方法或访问其状态。它们只能通过异步发送消息进行通信。
并发处理:每个 Actor 内部是串行处理消息的。它一次只处理一条消息。这意味着在处理一条消息时,它的状态是安全且一致的,无需加锁。
地址寻址:要向一个 Actor 发送消息,你必须知道它的地址。
响应式行为:Actor 在收到消息后,可以做出以下一种或多种反应:
改变其内部状态(为处理下一条消息做准备)。
向其他 Actor 发送有限条新消息。
创建有限个新 Actor。
决定如何处理下一条消息(即改变其行为)。
4. 一个生动的例子:银行转账
假设有两个用户 Actor:ActorA(张三)和 ActorB(李四),以及一个银行账户管理器 AccountManager。
传统加锁方式(易死锁):
线程锁定张三的账户。
线程尝试锁定李四的账户。
如果同时有另一个转账从李四到张三,就可能发生两个线程互相等待,导致死锁。
Actor 方式:
ActorA向AccountManager发送一条消息:“请从我的账户转 100 元给ActorB”。AccountManager的邮箱收到这条消息。AccountManager处理这条消息:它先检查
ActorA的余额是否充足。然后减少
ActorA账户的余额(修改自己的状态)。增加
ActorB账户的余额(修改自己的状态)。向
ActorA发送一条消息:“转账成功”。向
ActorB发送一条消息:“你收到了 100 元来自张三”。
所有操作都是
AccountManager串行处理的,在处理这条转账消息时,不会同时处理其他修改账户状态的消息,因此不存在竞态条件。邮箱中的消息会排队等待处理。
5. 优点
封装与状态隔离:状态被隐藏在 Actor 内部,避免了意外的并发修改。
强大的容错性:可以采用“let it crash”哲学。可以创建监督层次结构,一个 Actor(监督者)负责监控其子 Actor 的健康状况。如果子 Actor 崩溃了,监督者可以决定如何恢复(重启、重置、上报等),而不影响整个系统。
位置透明性:消息发送的机制是统一的。无论目标 Actor 是在本地进程的另一线程、另一台机器上,还是在另一个数据中心,代码看起来都是一样的。这使得分布式系统的开发变得简单。
可扩展性:由于没有共享状态和锁的争用,系统可以通过在更多 CPU 核心或更多机器上增加 Actor 的数量来轻松扩展。
6. 缺点与挑战
消息传递延迟:异步消息通信比直接方法调用开销更大。
编程模型复杂性:思维需要从“调用-返回”的指令式编程转变为“消息-响应”的响应式编程,有一定学习曲线。
消息可能丢失:在网络分布式环境中,消息可能无法送达(At-most-once 投递)。通常需要通过额外的确认和重传机制来实现At-least-once投递,但这可能导致消息重复。
系统整体行为难以理解:由于没有明确的调用栈,全是异步事件,调试和追踪一个跨越多 Actor 的请求会非常困难。
7. 主要实现与应用
Erlang/OTP:最著名和最成功的 Actor 模型实现,广泛应用于电信、金融等领域,以其“九个9”(99.9999999%)的惊人可靠性而闻名。WhatsApp 的后端就大量使用了 Erlang。
Akka (for Java/Scala): 是 JVM 平台上最流行的 Actor 模型工具包。它提供了构建高并发、分布式和弹性消息驱动应用程序所需的一切。
Orleans :微软开发的 .NET 框架,采用“虚拟 Actor”模型,简化了分布式编程。
其他语言:许多现代语言如 Go (goroutine + channel), Rust (
actixcrate), 和 Pony 都深受 Actor 模型思想的影响或提供了相关实现。