[SC] 可发送
Source: Dev.to
问题
为什么编译器需要知道一个值是否线程安全?
编译器必须确保一个值能够在隔离域之间传递而不会导致数据竞争。如果该值不安全,编译器会发出警告或错误,以避免出现不可确定的行为。
什么是 isolation domain(隔离域),它有什么作用?
一个 隔离域 定义了一条边界,在此边界内可以安全地访问值或引用而不会产生数据竞争。共有三种类型:
| 类型 | 描述 |
|---|---|
nonisolated | 默认域;不强加并发限制。 |
actor | 每个 actor 实例都有自己的域。 |
| global actor | 多个类型、属性和函数共享的域(例如 @MainActor)。 |
nonisolated 代码对其他域状态有什么限制?
nonisolated是默认的隔离域,不对内部并发施加限制。- 可以毫无问题地修改同一类型中其他代码的状态。
- 不能修改其他隔离域的状态。
无状态方法示例(可以从任何线程调用):
// ⚠️ `nonisolated` 是冗余的,因为它是默认域。
nonisolated func add(a: Int, b: Int) -> Int {
a + b
}
func add(a: Int, b: Int) -> Int {
a + b
}为什么从外部访问 actor 的属性需要 await?
actor 保证其所有存储属性和方法在单线程安全环境中执行,从而避免数据竞争。要从其他域访问这些数据,需要使用 await 来:
- 切换到 actor 的上下文。
- 等待 actor 释放其独占权。
actor Library {
// 隔离属性;只能通过 `await` 读取。
var books: [String] = []
// actor 方法(从外部调用时隐式 async)。
func addBook(_ title: String) {
books.append(title)
}
func getBookList() -> [String] {
books
}
// `nonisolated` 方法:可以不使用 `await` 调用。
nonisolated func libraryName() -> String {
"A library of books"
}
}使用示例:
let library = Library()
// ❌ 不能同步调用 actor 方法。
library.addBook("hola") // 编译错误
Task {
await library.addBook("Dopamine Nation")
let books = await library.getBookList()
print(books) // ["Dopamine Nation"]
// 直接读取属性(需要 await)。
await library.books.forEach { print($0) }
// ❌ 不能在 actor 外部修改属性。
// library.books.append("Another") // 错误
}global actor(全局 actor)如 @MainActor 与普通 actor 有何区别?
- 普通 actor:每个实例拥有自己的隔离域。
- 全局 actor:所有标记了该 actor 的实例共享同一个域(例如
@MainActor)。当应用的多个部分需要在相同的并发约束下运行时,这非常有用。
公共 API 要被视为线程安全并符合 Sendable 协议,需要满足哪些条件?
当一个接口满足以下条件时,即被认为在隔离域之间是安全的:
- 不公开可变的公共成员。
- 实现了自己的内部锁机制(例如
NSLock、DispatchQueue)。 - 其可变成员采用 copy‑on‑write(如值类型)。
标准库中许多类型已经遵循 Sendable。编译器也可以推断隐式符合:
// ✅ 隐式符合,因为其所有成员都是 Sendable成员是 Sendable。
struct SomeStruct {
var someIntValue: Int
}// ❌ 不会隐式符合;类是引用类型。
class SomeClass {
var someIntValue: Int = 0
}背诵
为什么带有
Int属性的struct会隐式符合Sendable,而具有相同属性的class则不符合?
因为struct是值类型;复制时会创建一个独立的新实例,从而避免数据竞争。而class是引用类型,可能在多个隔离域中被同时修改。你能用自己的话解释一下,当一个值在两个隔离域之间传递时会发生什么吗?
Swift 会检查该值是否符合Sendable。如果符合,编译器就允许其转移,并保证不会对同一数据产生未同步的并发访问。什么时候会有意义…? (根据你的需求继续补充).
如何在 actor 中将方法标记为 nonisolated?
如果不需要保护 actor 的状态,可以使用 nonisolated 标记。例如,当方法 返回某个标量或常量值 时。
Review
1. 公共 API 在哪些三种条件下可以安全地在并发域之间使用?
- 不可变性 – 暴露的值在创建后不能被修改。
- Sendable – 所有涉及的类型都遵循
Sendable协议。 - 无副作用 – API 不会访问或修改可能在线程之间共享的外部状态。
2. Swift 6.2 对 Sendable 默认行为有什么改变?
Swift 6.2 引入了 nonisolated(nonsending),它允许函数和闭包 在不跨越隔离边界的情况下不必是 Sendable。这样,编译器在 nonisolated 上下文中不再自动强制符合 Sendable,从而更容易与不需要在并发域之间传递的代码进行互操作。
参考文献
- Van der Lee, A. (2025). Swift Concurrency Course [在线课程]. avanderlee.com