Swift 的 actor 模型是从 Swift 5.5 引入的一种新型并发模型,用于帮助开发者编写安全、高效的并发代码。它的核心思想是通过将共享状态限制在 actor 中,从而减少数据竞争(data race)和同步错误的发生。
核心概念
1. actor 是一种引用类型
它类似于 class,但它有自己的隔离机制,用于在并发环境中管理共享状态。
actor BankAccount { var balance: Double = 0.0 func deposit(amount: Double) { balance += amount } func withdraw(amount: Double) -> Bool { guard balance >= amount else { return false } balance -= amount return true } }
- 在 actor 内部,属性和方法是线程安全的。
- 外部调用 actor 的方法时,会自动使用异步调用。
2. 数据隔离
Actor 确保只有一个任务可以同时访问其内部状态。也就是说,actor 内部的属性默认是受保护的,不能在多个线程中并发访问。
let account = BankAccount() Task { await account.deposit(amount: 100.0) let success = await account.withdraw(amount: 50.0) print("Withdrawal success: \(success)") }
3. 异步访问
由于 actor 的状态是隔离的,外部访问需要通过 await 来处理。这样可以让编译器强制开发者意识到可能存在的异步操作。
4. 不可变值可以直接访问
actor 内的常量(let 声明)或 nonisolated 修饰的属性,可以直接被外部同步访问,因为它们是线程安全的。
actor Counter { let name: String var value: Int = 0 init(name: String) { self.name = name } func increment() { value += 1 } } let counter = Counter(name: "Test Counter") print(counter.name) // 可直接访问
5. 跨 Actor 通信
如果一个 actor 需要与另一个 actor 交互,通信也是通过异步的方式完成的。
actor Logger { func log(message: String) { print("Log: \(message)") } } actor Worker { let logger = Logger() func doWork() async { await logger.log(message: "Work started") } }
和其他并发模型的比较
与 GCD 和锁的区别
- actor 抽象了低层的线程管理和锁机制,让开发者更关注业务逻辑,而不是同步细节。
与 class 的区别
actor 和 class 的核心区别是线程安全性:
- class 的属性需要手动管理线程安全。
- actor 的属性默认是线程安全的。
与其他语言(如 Kotlin 或 Java 的 Actor 模型)的比较
- Swift 的 actor 更加现代化,结合了 Swift 的异步/等待语法,使得代码更加清晰和易读。
注意事项
1. 性能开销
虽然 actor 提供了线程安全性,但它也带来了上下文切换和异步调用的开销。在性能关键场景中,可能需要权衡使用 actor 的利弊。
2. actor 不适合所有场景
对于简单的线程安全需求,可能使用 DispatchQueue 或 lock 更直接。
3. 与现有代码集成
如果你的项目中有大量现有代码,迁移到 actor 模型需要一定的改动。
适用场景
- 需要在多线程环境中共享状态,但不想手动管理锁。
- 数据竞争可能会引发严重问题的场景。
- 想要简化并发代码并提高代码可读性。