1️⃣ 结论
Actor 内部的状态(属性)是串行化访问的,只允许通过 Actor 的执行上下文访问,因此不需要额外锁,天然线程安全。
2️⃣ Actor 如何保证安全
① 单一执行上下文(Executor)
- 每个 Actor 都有自己的 executor,类似一个队列。
- 同一时间只能有一个任务在执行 Actor 的方法或访问属性。
- 多个任务同时访问 Actor 状态时,会排队顺序执行。
Actor BankAccount (executor)
├─ Task 1 accessing balance
├─ Task 2 queued
└─ Task 3 queued
- Task 2、3 必须等 Task 1 完成,保证状态访问不会并发冲突。
② async 消息传递
- 跨 Actor 调用必须使用
await:
await account.deposit(100)
- 调用被封装成一个任务,放入 Actor 的 executor 队列。
- 即使多个调用同时发起,也会排队执行,保证 属性修改互不干扰。
③ Actor 内部同步
- Actor 内部方法可以直接访问属性,无需加锁。
- 只有跨 Actor 调用才是异步的。
actor Counter {
private var value = 0
func increment() {
value += 1 // 安全
}
func getValue() -> Int {
value // 安全
}
}
- 即使多个 Task 同时调用
increment,Swift runtime 会排队执行,保证线程安全。
3️⃣ 对比传统锁机制
| 特性 | Actor | 锁(Mutex / NSLock) |
|---|---|---|
| 并发安全 | ✅ 内部属性串行访问 | ✅ 需要手动加锁 |
| 死锁风险 | ❌ 消息队列顺序执行 | ✅ 高风险 |
| 可组合性 | ✅ async/await 天然支持 | ❌ 异步组合复杂 |
| 可推理性 | 高 | 低 |
Actor 通过“串行化访问 + 异步消息”天然消除了数据竞争。
4️⃣ 小陷阱
-
跨 Actor 调用必须
await- 否则编译器报错,避免直接并发访问状态。
actor BankAccount {
var balance = 0
}
let account = BankAccount()
account.balance += 10 // ❌ 编译错误
await account.balance // ✅ 正确
-
Actor 内部可以安全同步
- Actor 内部方法和属性可以直接访问,不需锁。
-
Actor 内部调用跨 Actor 仍需注意顺序
- 如果两个 Actor 相互调用,可能需要设计好访问顺序,否则可能死锁或延迟。
5️⃣ 面试级总结
Swift Actor 的内部属性是线程安全的,因为 Actor 保证了同一时间只有一个任务访问其状态。它通过 executor 串行化所有任务,并要求跨 Actor 调用使用 async/await,从而实现天然的数据隔离和并发安全。相比传统锁,Actor 更易推理、更安全,也更易组合异步任务。