在 Swift 中,Sendable Types 是与并发编程相关的一个概念,目的是帮助开发者在多线程环境下确保类型的安全性,避免数据竞态(data races)。随着并发模型的引入,特别是在 Swift 5.5 中引入了 async/awaitactor,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 类型的规则

  1. 基本类型:许多基础类型默认就是 Sendable,比如 IntDoubleStringArray 等,因为它们是值类型(value types),而值类型的实例是安全地在多个线程或任务之间传递的。

  2. 引用类型:对于引用类型(class 类型),它们默认是不可 Sendable 的。因为引用类型是共享的,多个线程可能同时访问或修改它们的状态,因此需要显式地进行同步。可以通过使类遵守 Sendable 协议来标记它为线程安全,或者将它们包装成一个值类型(如 struct)来实现线程安全。

  3. 结构体和枚举:结构体和枚举本质上是值类型,通常是 Sendable 的。唯一需要注意的是,如果它们包含引用类型的属性,那么这些引用类型必须显式符合 Sendable 协议,才能使整个结构体或枚举符合 Sendable 协议。

  4. 非 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 的,因为它们的值不会在并发环境中改变,从而避免了数据竞争。
  • 类类型:如果类中含有状态改变的方法,它们可能需要实现线程安全机制,如使用锁来保护数据。

总结

  1. Sendable 是 Swift 5.5 中引入的一个协议,标记了可以安全地跨线程和并发任务传递的类型。
  2. 符合 Sendable 协议的类型可以保证在并发任务中不会导致数据竞态问题。
  3. 基本数据类型和许多结构体默认是 Sendable 的,但引用类型(class)通常不是 Sendable,除非显式遵循该协议并确保线程安全。
  4. 编译器会在跨线程传递类型时进行检查,确保只有符合 Sendable 协议的类型才会被传递,从而提高并发编程的安全性。

通过这种机制,Swift 帮助开发者在并发编程中更好地管理数据的安全性,避免出现数据竞争和不一致的状态。