mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1218 字
3 分钟
Actor 模型(Actor Model)
2026-06-13

一、为什么需要 Actor 模型#

并发编程最头疼的问题是什么?共享可变状态。两个线程同时修改一个变量,你需要加锁;锁加多了,死锁就来了;锁粒度粗了,性能又上不去。更麻烦的是,bug 难以复现——今天跑得好好的,明天换个负载就崩了。

Actor 模型从根本上消除了这个问题。它的核心主张是:不要共享状态,只传递消息。每个 Actor 独占自己的状态,外部只能通过发消息来影响它,而 Actor 一次只处理一条消息,所以不需要锁。没有共享状态,就没有数据竞争;没有锁,就没有死锁——至少理论上如此。

Erlang 用这套模型在电信系统里跑了三十多年,实现了「九个九」(99.9999999%)的可用性。Akka 把它带到了 JVM 生态。游戏服务器、聊天系统、物联网平台——这些天然适合 Actor 模型的场景,都有一个共同特点:大量独立实体各自维护状态,偶尔交互。

二、现实类比#

同事之间只通过密封信件和信箱沟通。没人直接走进别人的办公室翻他的文件——你写一封信,投到他的信箱,然后回去继续自己的工作。每个人一次只处理一封信,处理完再拆下一封。

三、核心思想#

Actor 是拥有私有状态和信箱(消息队列)的轻量级进程。Actor 之间仅通过发送异步消息通信。每个 Actor 一次处理一条消息,更新状态并可选地向其他 Actor 发送消息。

flowchart LR subgraph ActorA["Actor A"] A1[State: count=3] A2[Mailbox: m1 m2 m3] A3[Processing: m1] end subgraph ActorB["Actor B"] B1[State: items=[]] B2[Mailbox: m4 m5] B3[Processing: m4] end subgraph ActorC["Actor C"] C1[State: total=0] C2[Mailbox: m6] C3[Idle] end ActorA -- send --> ActorB
属性
并发无共享状态——仅消息传递
处理每个 Actor 串行(一次一条消息)
故障隔离Actor 崩溃不会损坏其他 Actor
可扩展性数百万轻量级 Actor(Erlang: 每进程约 2KB)

四、变体与对比#

模式关系区别
事件循环每个 Actor 类似信箱上的单线程事件循环事件循环侧重 I/O 多路复用;Actor 侧重状态隔离和消息传递
观察者模式Actor 通过消息通信类似发布/订阅观察者是对象间通知机制;Actor 是并发计算模型
CSP 模型都通过消息通信CSP(Go channel)强调同步通信和通道;Actor 强调异步发送和信箱
状态机Actor 行为通常遵循状态机模式状态机描述行为转换;Actor 描述并发实体
Warning

Actor 没有共享状态和锁,但不意味着不会死锁。如果 Actor A 等待 Actor B 的回复,同时 Actor B 也在等待 Actor A 的回复,双方都无法处理对方的消息——这本质上就是死锁。

五、多语言实现#

Go:基于 goroutine + channel 的 Actor#

Go 没有内置 Actor 框架,但 goroutine + channel 天然适合实现 Actor 模式:

type Actor struct {
state interface{}
mailbox chan interface{}
handler func(state interface{}, msg interface{}) interface{}
}
func NewActor(initial interface{},
handler func(interface{}, interface{}) interface{}) *Actor {
a := &Actor{
state: initial,
mailbox: make(chan interface{}, 100),
handler: handler,
}
go a.run() // 启动 Actor 的消息循环
return a
}
func (a *Actor) Send(msg interface{}) {
a.mailbox <- msg
}
func (a *Actor) run() {
// 一次只处理一条消息,天然串行
for msg := range a.mailbox {
a.state = a.handler(a.state, msg)
}
}

TypeScript:带信箱的 Actor#

type MessageHandler<S> = (state: S, msg: unknown) => S;
class Actor<S> {
private state: S;
private mailbox: unknown[] = [];
private processing = false;
constructor(initialState: S, private handler: MessageHandler<S>) {
this.state = initialState;
}
send(msg: unknown): void {
this.mailbox.push(msg);
if (!this.processing) this.processMailbox();
}
private processMailbox(): void {
this.processing = true;
while (this.mailbox.length > 0) {
const msg = this.mailbox.shift()!;
this.state = this.handler(this.state, msg);
}
this.processing = false;
}
getState(): S {
return this.state;
}
}
// 使用示例:计数器 Actor
const counter = new Actor({ count: 0 }, (state, msg) => {
if (msg === 'increment') return { count: state.count + 1 };
if (msg === 'decrement') return { count: state.count - 1 };
return state;
});
counter.send('increment');
counter.send('increment');

六、生产验证#

  • Erlang/OTPerl_process.h#L1043-L1205:BEAM 虚拟机中进程的表示,sig_qs 是消息队列(信箱),每个进程拥有独立堆和信箱,轻量级 Actor 的工业级实现
  • Akka (Scala)Actor.scala#L476-L547:核心 Actor 接口,通过偏函数 receive 指定消息处理行为,aroundReceive 是分发钩子
  • Actix (Rust) — Rust 生态中最流行的 Actor 框架,基于 Tokio 运行时,提供类型化消息和监督机制

七、小结#

何时使用:

  • 分布式系统——Actor 自然映射到网络节点(Erlang/OTP、Akka Cluster)
  • 游戏服务器——每个实体(玩家、NPC、房间)作为独立 Actor
  • 聊天系统——每个对话/聊天室作为一个 Actor
  • 电信/物联网——大量并发会话,每个设备作为 Actor

何时不用:

  • 紧密数据耦合——组件需要频繁共享可变状态时,消息传递增加延迟
  • 简单请求-响应——函数调用比 Actor 往返更简单直接
  • 计算密集无并发——没有并发收益的 Actor 只有开销
  • 强一致性需求——Actor 提供最终一致性;ACID 事务需要其他机制

八、参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

Actor 模型(Actor Model)
https://blog.souloss.com/posts/programming/concurrency/concurrency-actor-model/
作者
Tsukimi
发布于
2026-06-13
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时