首页 Swift 并发框架之Sendable
文章
取消

Swift 并发框架之Sendable

前言

在 Swift 中,Sendable 是一个协议,用于声明遵循该协议的类型是“可发送的”。具体来说,如果一个类型被声明为 Sendable,那么就可以安全地从一个任务(task)传递到另一个任务,而无需担心数据竞争或并发访问的问题。这样可以帮助开发者标识出哪些类型可以安全地在不同的并发执行上下文中传递和共享。

Sendable

1
2
3
/// The Sendable protocol indicates that value of the given type can
/// be safely used in concurrent code.
public protocol Sendable {}

Sendable 是一个空协议,用于向外界声明实现了该协议的类型在并发环境下可以安全使用,更准确的说是可以自由地跨 actor 传递。Sendable 有一个专属名称 Marker Protocols,特征主要有:

  • 具有特定的语义属性 (semantic property),并且是编译期属性而非运行时属性
    • Sendable 的语义属性就是要求并发下可以安全地跨 actor 传递
  • 协议体必须为空
  • 不能继承自 non-marker protocols
  • 不能作为类型名用于 isas?等操作
  • 不能用作泛型类型的约束

比如值类型在 传递是是会执行拷贝操作的,也就是跨Actor传递是安全的,所以这些类型隐式遵守了Sendable.

  • 基础类型,IntStringBool
  • 所含元素类型符合 Sendable 协议的集合,如:ArrayDictionary
  • 不含有引用类型成员的 struct、引用类型关联值的 enum

所有 actor 也是自动遵守了Sendable 协议

1
2
3
4
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol Actor : AnyObject, Sendable {
    nonisolated var unownedExecutor: UnownedSerialExecutor { get }
}

class 遵循 Sendable 协议,并有以下限制 (确保实现了 Sendable 协议的类数据安全的):

  • class 必须是 final,否则有 Warning: Non-final class ‘X’ cannot conform to ‘Sendable’; use ‘ @unchecked Sendable’
  • class 的存储属性必须是 immutable,否则有 Warning: Stored property ‘x’ of ‘Sendable’-conforming class ‘X’ is mutable
  • class 的存储属性必须都遵守 Sendable 协议,否则 Warning: Stored property ‘y’ of ‘Sendable’-conforming class ‘X’ has non-sendable type ‘Y’
  • class 的祖先类 (如有) 必须遵守 Sendable 协议或者是 NSObject,否则 Error: ‘Sendable’ class ‘X’ cannot inherit from another class other than ‘NSObject’

比如一下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User {
  var name: String
  var age: Int
}
extension UserManager {
  func user() async -> User {
    // Warning: Non-sendable type 'User' returned by implicitly asynchronous call to actor-isolated instance method 'user()' cannot cross actor boundary
    return await bankAccount.user()
  }
}

// 修改
struct User {
  var name: String
  var age: Int
}
// 或者
final class User: Sendable {
  let name: String
  let age: Int
}

@Sendable

@Sendable 是一个属性包装器,被 @Sendable 修饰的函数、闭包可以跨 actor 传递。通过将 @Sendable 应用于闭包类型,可以确保这些闭包可以安全地在不同的任务之间传递,并且不会引起数据竞争或并发访问的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
actor BankAccount {
    let accountNumber: Int
    var balance: Double

    init(accountNumber: Int, initialDeposit: Double) {
        self.accountNumber = accountNumber
        self.balance = initialDeposit
    }
 
    func addBalance(amount: Double, completion: @Sendable (Double) -> Void) {
        balance += amount
        completion(balance)
    }
}

class AccountManager {
    let bankAccount = BankAccount.init(accountNumber: 123456789, initialDeposit: 1000)
    func addAge() async {
        // Wraning: Non-sendable type '(Int) -> Void' passed in implicitly asynchronous call to actor-isolated instance method 'addAge(amount:completion:)' cannot cross actor boundary
        await bankAccount.addBalance(amount: 52, completion: { balance in
            print(balance)
        })
    }
}

@Sendable 修饰 Closure,告诉 Closure 可能会在并发环境下调用,请注意数据安全!当然编译器会对 @Sendable Closure 的实现进行各种合规检查:

  • 不能捕获 actor-isolated 属性,否则 Error: Actor-isolated property ‘x’ can not be referenced from a Sendable closure
  • 不能捕获 var 变量,否则 Error: Mutation of captured var ‘x’ in concurrently-executing code
  • 所捕获对象必须实现 Sendable 协议,否则 Warning: Capture of ‘x’ with non-sendable type ‘X’ in a @Sendable closure。

总结

Sendable@Sendable 提供了一种在并发代码中明确声明类型和闭包是否是“可发送的”的方式,从而帮助开发者编写更安全、更可靠的并发代码

  • Sendable 是一个 Marker Protocol,用于编译期的合规检查
  • 所有值类型都自动遵守 Sendable 协议
  • 所有遵守 Sendable 协议的类型都可以跨 actor 传递
  • @Sendable 用于修饰方法、闭包
  • 在并发环境下执行的闭包都应用 @Sendable 修饰
本文由作者按照 CC BY 4.0 进行授权

Swift 并发框架之Task & Task Group

-