2-14.【Concurrency】Actor 是如何保证数据隔离的?与锁机制相比有什么优势?

3 阅读2分钟

一句话核心

Actor 通过将其状态完全隔离在单一执行上下文(executor)内,只允许通过异步消息(async 方法)访问,从而保证数据安全,不需要显式锁。


1️⃣ Actor 的基本概念

actor BankAccount {
    private var balance: Int = 0

    func deposit(_ amount: Int) {
        balance += amount
    }

    func withdraw(_ amount: Int) -> Bool {
        if balance >= amount {
            balance -= amount
            return true
        } else {
            return false
        }
    }

    func getBalance() -> Int {
        balance
    }
}

特点:

  1. 状态隔离

    • balance 只能被 actor 自己访问
  2. 异步访问

    • 跨 actor 调用必须 await
  3. executor 单线程保证

    • actor 内部执行上下文串行化

2️⃣ 数据隔离如何实现

执行上下文(executor)

  • 每个 actor 有自己的 executor
  • actor 内部任务顺序执行
  • 同一时间只有一个任务能访问 actor 的状态
Actor (executor)
 ├─ Task 1 accessing balance
 ├─ Task 2 queued
 └─ Task 3 queued
  • Task 2/3 等待 Task 1 完成
  • 不会出现数据竞争
  • 不需要锁

async 消息传递

await account.deposit(100)
  • 调用会被封装成 消息任务
  • 放入 actor 的 executor 队列
  • 异步顺序执行
  • 任何跨 actor 访问都通过 await 调度

3️⃣ 与传统锁机制对比

特性Actor锁(Mutex / NSLock)
数据隔离✅ 完全隔离❌ 手动保护
锁粒度单个 actor任意共享资源
死锁风险❌ 消息队列顺序执行✅ 高(容易死锁)
并发可推理性低(必须仔细分析临界区)
可组合性✅ async/await 支持跨 actor❌ 复杂,需要手动管理
可扩展性高(多 actor 并发)受锁冲突限制

4️⃣ Actor 的核心优势

  1. 消除数据竞争

    • 不需要手动加锁,状态隔离 + executor 串行执行
  2. 消除死锁 / 优先级反转问题

    • 内部消息队列顺序处理
  3. 与 async/await 天然整合

    • 跨 actor 调用是异步的 → 自然等待
  4. 可组合性强

    • Actor 之间可以同时处理多个任务(不同 executor)

5️⃣ 小陷阱(必须知道)

  1. 跨 actor 调用必须 await

    await account.deposit(100) // 必须 await
    
    • 否则编译错误
  2. actor 内部同步访问安全

    func transfer(to other: BankAccount, amount: Int) async {
        if await withdraw(amount) {
            await other.deposit(amount)
        }
    }
    
    • 跨 actor 可能需要处理死锁或顺序问题
    • 但 actor 内状态是安全的

6️⃣ 直观类比

  • Actor = “单线程服务 + 异步消息队列”
  • 锁机制 = “共享房间,谁先锁谁进房间”
  • Actor 不用抢房间,状态独占;锁机制需要你自己管理房间的开关和优先级。

7️⃣ 面试必背总结

Swift 的 Actor 通过 executor 串行化其内部状态访问、隔离数据,只能通过异步方法访问,从而消除了数据竞争和死锁风险;相比传统锁机制,Actor 提供了更安全、更可推理、更易组合的并发模型。