Sendable 协议通过限制只能传递那些本身线程安全或不可变的类型,来避免这种情况
Sendable 协议是 Swift 并发模型中的一个非常重要的特性,它的主要作用是标记一个类型可以在并发环境中安全地传递。通过对符合 Sendable 协议的类型进行约束,Swift 在编译时确保类型的安全性,避免在多个并发任务中传递数据时出现 数据竞争。
具体来说,Sendable 协议是如何确保可以安全地在并发任务间传递的类型的呢?这涉及到 类型的不可变性、数据竞争的预防以及 对类型的内存管理的静态检查。下面我会详细解释它是如何做到这点的。
并发环境中的传递安全性:在多线程并发环境下,如果你传递的数据包含可以被多个线程修改的状态,就可能会引发 数据竞争,从而导致未定义的行为。Sendable 协议通过限制只能传递那些本身线程安全或不可变的类型,来避免这种情况。
例如,Sendable 允许我们把类型传递到不同的并发任务或队列中,前提是这个类型是不可变的,或者其内部的状态是同步的。
不可变类型:如果一个类型是不可变的(例如结构体或常量类型),它可以安全地在多个并发任务间传递,因为它的状态不会改变,任何任务都不会对其内部数据进行修改。
例如:
struct Point: Sendable {
let x: Double
let y: Double
}
这个 Point 结构体是不可变的,它的值一旦创建就无法更改。因此,它可以在不同的任务或线程中安全地传递。
不可变的引用类型:如果一个引用类型的实例不能被修改,那么它也可以符合 Sendable 协议。例如,String、Array 这些标准库类型可以是 Sendable,因为它们本身是不可变的。
例如:
let name: String = "Sendable"
String 类型本身是不可变的,因此可以在不同的并发任务间安全地传递。
显式符合 Sendable 的可变类型:如果类型是可变的,并且开发者明确保证了它在并发环境下是安全的,那么这个类型也可以符合 Sendable 协议。通常这意味着开发者需要手动管理并发访问,确保没有多个任务在同一时间修改它的状态。
例如,如果你想让一个结构体成为 Sendable,但它包含可变的属性,你可能需要在每个可变属性上使用锁或者其他线程同步机制来确保线程安全:
struct Counter: Sendable {
private var value = 0
private let lock = NSLock()
mutating func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
在这个例子中,我们使用 NSLock 来确保 value 在并发环境中的安全修改,因此这个类型是符合 Sendable 协议的。
不可变数据传递:当你传递一个不可变类型的实例(例如结构体或 String)到多个线程时,由于不可变性保证了数据在传递后不会改变,因此不会发生数据竞争,线程之间互不干扰。比如你传递一个不可变的结构体 Point,无论多少个任务访问它,都不会出现竞争条件。
可变数据传递的控制:如果你传递的是一个可变的数据类型(如一个自定义的类),那么你需要显式地保证它在多个线程访问时是线程安全的。Sendable 协议本身并不强制要求你使用某种特定的线程同步机制(比如锁),但是你必须自行确保其安全性。编译器不会允许不安全的类型实现 Sendable。
不可变性:如果一个类型的属性或状态是不可变的,编译器允许它符合 Sendable 协议。
线程安全性:对于可变类型,编译器会检查它是否符合并发访问的要求。如果一个类型没有适当地同步或没有明确声明为符合 Sendable 协议,那么它就不能在并发任务之间传递。
举个例子,如果你尝试在并发任务间传递一个非 Sendable 类型的对象,编译器会发出警告或错误,提示你可能存在数据竞争问题:
class UnsafeClass {
var value = 0
}
let unsafeObject = UnsafeClass()
Task {
// 错误:UnsafeClass 不符合 Sendable 协议
await someAsyncFunction(object: unsafeObject)
}
上面代码中的 UnsafeClass 是一个可变的引用类型,且没有进行任何线程安全的处理,因此它不能直接用于并发任务间的传递。
例如:
actor MyActor {
var value = 0
}
func performTask(actor: MyActor) async {
// 错误:MyActor 不符合 Sendable 协议,不能在并发任务中传递
await someAsyncFunction(actor: actor)
}
Task {
let actor = MyActor()
await performTask(actor: actor)
}
在这个例子中,MyActor 是一个 可变的 actor,它不符合 Sendable 协议,因此它不能作为参数传递给 Task 中的并发函数。如果你需要将一个 actor 传递到另一个任务中,你必须确保它符合 Sendable 协议,或者使用其他的线程安全机制。
不可变类型 默认是 Sendable,因为它们在并发环境下是安全的。
可变类型 如果符合线程安全的要求,才能通过实现 Sendable 协议来保证并发访问的安全性。
编译器检查:Swift 编译器会检查类型是否符合 Sendable 协议,防止不符合要求的类型在并发任务间传递,避免数据竞争。