在 Swift 中,Sendable Types 是与并发编程相关的一个概念,目的是帮助开发者在多线程环境下确保类型的安全性,避免数据竞态(data races)。随着并发模型的引入,特别是在 Swift 5.5 中引入了 async/await
和 actor
,Swift 需要一种机制来确保在多个任务、线程或队列之间传递数据时的类型安全。Sendable Types 就是为了满足这一需求而设计的。
什么是 Sendable Types?
Sendable Types 是一种能够安全地跨线程或任务传递的类型。在多线程或并发任务中,当你将数据传递给另一个线程或任务时,必须确保这些数据在不同的执行环境中能安全地共享或复制,而不会导致不可预测的结果或数据竞态。只有符合 Sendable 协议的类型才能在并发环境中安全地传递。
Sendable 协议
Sendable
协议是 Swift 提供的一种协议,任何类型只要符合 Sendable
协议,就可以安全地在不同的任务、线程或队列之间传递。Sendable
协议确保了传递的数据不会被多个线程同时访问,从而避免数据竞态。
Sendable 协议声明
@available(Swift 5.5, *)
public protocol Sendable {}
为什么需要 Sendable Types?
在并发编程中,多个任务或线程可能同时访问和修改相同的数据。这可能导致数据的不一致性和竞态条件。为了避免这种情况,Swift 引入了 Sendable
协议来确保类型在跨线程或任务传递时,数据是安全的。
- 如果一个类型是
Sendable
,那么它的实例可以在并发任务中安全地传递。 - 如果一个类型不是
Sendable
,那么它的实例不能在并发任务之间安全地传递,编译器会报错。
Sendable 类型的规则
-
基本类型:许多基础类型默认就是
Sendable
,比如Int
、Double
、String
、Array
等,因为它们是值类型(value types),而值类型的实例是安全地在多个线程或任务之间传递的。 -
引用类型:对于引用类型(
class
类型),它们默认是不可Sendable
的。因为引用类型是共享的,多个线程可能同时访问或修改它们的状态,因此需要显式地进行同步。可以通过使类遵守Sendable
协议来标记它为线程安全,或者将它们包装成一个值类型(如struct
)来实现线程安全。 -
结构体和枚举:结构体和枚举本质上是值类型,通常是
Sendable
的。唯一需要注意的是,如果它们包含引用类型的属性,那么这些引用类型必须显式符合Sendable
协议,才能使整个结构体或枚举符合Sendable
协议。 -
非 Sendable 类型:如果一个类型没有符合
Sendable
的规则(例如,含有非Sendable
类型的属性),那么该类型就不是Sendable
的,不能在并发任务中传递。
例子
1. Sendable 类型的基本示例
struct Point: Sendable {
var x: Int
var y: Int
}
let point = Point(x: 10, y: 20)
Task {
// 可以安全地将 point 传递给另一个异步任务
print(point)
}
在这个例子中,Point
是一个结构体,符合 Sendable
协议,因此它的实例可以安全地跨任务传递。
2. 非 Sendable 类型示例
class SharedData {
var value: Int = 0
}
let data = SharedData()
Task {
// 编译错误,SharedData 类型不是 Sendable
// print(data)
}
在这个例子中,SharedData
是一个类,类是引用类型,默认不是 Sendable
的。因此,它的实例不能在并发任务之间传递。为了使它成为 Sendable
类型,可以显式地让它遵循 Sendable
协议,并确保它的数据是线程安全的。
3. 使引用类型符合 Sendable
class SafeData: Sendable {
private var value: Int = 0
func updateValue(_ newValue: Int) {
value = newValue
}
func getValue() -> Int {
return value
}
}
let safeData = SafeData()
Task {
// SafeData 符合 Sendable,可以在并发任务中安全地传递
await safeData.updateValue(10)
}
在这个例子中,SafeData
遵循了 Sendable
协议并确保线程安全,因此它的实例可以在并发任务之间安全传递。
Sendable 类型的限制
- 不可变数据:对于一些不可变的数据类型(如常量),它们通常是
Sendable
的,因为它们的值不会在并发环境中改变,从而避免了数据竞争。 - 类类型:如果类中含有状态改变的方法,它们可能需要实现线程安全机制,如使用锁来保护数据。
总结
- Sendable 是 Swift 5.5 中引入的一个协议,标记了可以安全地跨线程和并发任务传递的类型。
- 符合
Sendable
协议的类型可以保证在并发任务中不会导致数据竞态问题。 - 基本数据类型和许多结构体默认是
Sendable
的,但引用类型(class
)通常不是Sendable
,除非显式遵循该协议并确保线程安全。 - 编译器会在跨线程传递类型时进行检查,确保只有符合
Sendable
协议的类型才会被传递,从而提高并发编程的安全性。
通过这种机制,Swift 帮助开发者在并发编程中更好地管理数据的安全性,避免出现数据竞争和不一致的状态。