使用 Swift 并发消除数据竞争
Eliminate data races using Swift Concurrency
2022年6月6日
一句话判断
Doug Gregor 用航海比喻系统性地讲解了 Swift 并发模型如何通过隔离(Isolation)和 Sendable 协议在编译期防止数据竞争——这是理解 Swift 6 严格并发检查的基础。
这场 Session 讲了什么
Swift 团队的 Doug Gregor 从一个高层视角解释了 Swift 并发消除数据竞争的整体设计,而不是聚焦于某个具体的语言特性。
核心模型:并发中的任务是相互独立的”船”,每条船有自己的资源。船之间传递数据时,需要”海关检查员”(Swift 编译器)确保传递的数据是安全的。值类型(如 struct)传副本,天然安全;引用类型(如 class)传引用,会导致共享可变状态,产生数据竞争。
Sendable 协议是关键机制——只有满足 Sendable 的类型才能跨越隔离边界。编译器在 task 创建、闭包传递、actor 消息发送等多个检查点验证 Sendable 约束。Actor 提供了隔离的可变状态,通过异步消息传递安全地共享数据。
值得深挖的点
值类型 vs 引用类型在并发中的本质区别。 Pineapple(struct)传副本,修改不影响原件;Chicken(class)传引用,两边都能改同一个对象。这个区别在单线程代码中不那么重要,在并发中却是数据竞争的根源。Sendable 协议在编译期帮你区分这两者。
Sendable 的一致性规则。 Struct 和 enum 只要所有存储属性都是 Sendable 就自动满足(非 public 类型可以由编译器推断)。Class 要成为 Sendable 非常严格——必须是 final 且只有不可变存储,或者用 @unchecked Sendable 放弃编译器检查。后者意味着你自己承担正确性的责任。
Actor 的隔离模型。 Actor 内部的状态只能通过 actor 的方法访问,外部调用必须用 await,这实际上是一次异步消息传递。这种设计保证了 actor 的可变状态同一时刻只有一个任务在访问。
代码片段
// Sendable 类型的编译器检查
struct Pineapple: Sendable {
var weight: Double
var ripeness: Int
}
// 非 Sendable 类型
class Chicken {
var name: String
var isHungry: Bool
}
// 编译器阻止跨隔离边界传递非 Sendable 类型
func shareWithFriend() async {
let pineapple = Pineapple(weight: 1.2, ripeness: 5)
let chicken = Chicken(name: "Clucky", isHungry: true)
// 通过:Pineapple 是 Sendable
let task1 = Task { pineapple }
// 编译错误:Chicken 不是 Sendable,不能跨 task 边界传递
// let task2 = Task { chicken }
}
// Actor 提供隔离的可变状态
actor Coop {
private var chickens: [Chicken] = []
func add(_ chicken: Chicken) {
chickens.append(chicken)
}
func feedAll() {
for chicken in chickens {
chicken.isHungry = false
}
}
}
// 外部访问必须用 await(异步消息传递)
let coop = Coop()
await coop.add(Chicken(name: "Clucky", isHungry: true))
await coop.feedAll()
最佳实践
- 让数据类型尽量满足 Sendable。 优先使用值类型(struct/enum),如果必须用 class,确保它是不可变的或自己做了内部同步。
- 谨慎使用
@unchecked Sendable。 它绕过了编译器检查,如果你的内部同步有漏洞,数据竞争还是会发生。 - 理解 Sendable 检查点。 Task 创建、
@Sendable闭包、actor 消息传递——这些是编译器强制检查的边界。 - 从架构层面考虑并发。 Swift 并发不是一个局部优化,而是影响整体代码结构的编程模型。新项目应该从设计阶段就考虑隔离边界。
还有什么值得关注
- 推荐观看 2021 年的 Swift Concurrency 系列 Sessions 了解各个语言特性的细节
- Swift 6 的严格并发检查将进一步强化 Sendable 约束
@Sendable闭包标注暗示闭包可能在不同隔离域执行,捕获的值必须是 Sendable 的nonisolated关键字可以让 actor 的某些方法不参与隔离,用于纯计算场景