2-17.【Concurrency】actor 之间的消息传递是同步还是异步?它如何保证线程安全?

1 阅读2分钟

1️⃣ 核心结论

Actor 之间的消息传递是异步的(async),每个消息封装为任务排入目标 Actor 的 executor 队列,保证同一时间只有一个任务访问 Actor 内部状态,从而天然线程安全。

换句话说:

  • Actor 内部属性永远不会被多个线程同时访问
  • 跨 Actor 调用必须使用 await
  • executor 串行化任务执行

2️⃣ 消息传递的本质

① 什么是消息

actor BankAccount {
    func deposit(_ amount: Int) {
        balance += amount
    }
}
  • 当一个 Actor A 调用 B 的方法:
await account.deposit(100)
  • 编译器和 runtime 会做三件事:
  1. 封装调用为任务(message)

    • 包含方法、参数、continuation
  2. 放入目标 Actor 的 executor 队列

    • 任务排队执行
  3. 调用者 await 任务完成

    • await 等待任务执行完成返回结果

这就是异步消息传递,而非直接函数调用。


② executor 队列保证串行性

  • 每个 Actor 的 executor 维护一个任务队列
  • 同一时间只允许一个任务执行 Actor 内部代码
  • 所以 Actor 内部属性访问 天然串行化
Actor Executor Queue
 ├─ Task 1
 ├─ Task 2
 └─ Task 3
  • 任务逐个执行 → 无竞争

3️⃣ 线程安全是如何保证的

✅ 通过隔离和串行执行

  1. 状态隔离

    • Actor 内部属性只能通过 Actor 的 executor 访问
  2. 串行任务执行

    • 每个任务完成前,队列中的下一个任务无法访问属性
  3. 异步消息

    • 跨 Actor 调用必须 await,调用者等待执行完成,不能直接并发修改

结果:Actor 内部属性永远不会被多线程同时访问 → 无数据竞争


4️⃣ 示例

actor BankAccount {
    private var balance: Int = 0
    
    func deposit(_ amount: Int) {
        balance += amount
    }
    
    func transfer(to other: BankAccount, amount: Int) async {
        if balance >= amount {
            balance -= amount
            await other.deposit(amount)
        }
    }
}
  • transfer 调用跨 Actor other.deposit
  • 编译器强制 await → 消息异步发送给 other 的 executor
  • 目标 Actor 的状态不会被并发访问
  • 线程安全由 executor 串行执行保证

5️⃣ Actor 消息传递 vs 同步调用

维度Actor 消息传递同步函数调用
执行异步排队立即执行
线程安全✅ 单线程串行访问❌ 多线程需要锁
await必须不需要
可组合性高(跨 Actor async/await)低(容易死锁)

6️⃣ 直观比喻

  • Actor = “单线程仓库 + 消息队列”
  • 调用跨 Actor = “发请求给仓库,让仓库排队处理”
  • await = “等待仓库处理完成返回结果”
  • 内部状态不会被多个线程同时访问 → 安全

7️⃣ 面试必背总结

在 Swift 中,Actor 之间的消息传递是异步的,每个调用被封装为任务放入目标 Actor 的 executor 队列,串行执行 Actor 内部逻辑;这种机制保证了 Actor 内部状态天然线程安全,无需锁,也消除了数据竞争和死锁风险。