You ask:
在 Swift async/await 中,我可以使用 Lock 还是 Semaphore
简而言之,您可以,但要格外小心。您可以使用锁来同步对某些可变属性的快速访问。但不要使用锁来管理单独的 Swift 并发任务之间的依赖关系或任何可能会很慢的事情(例如,写入持久存储、执行网络请求等)。
在 WWDC 2021 视频中Swift 并发:幕后花絮 https://developer.apple.com/videos/play/wwdc2021/10254?time=1507,他们警告我们锁是允许的,但必须“谨慎”使用,即在非常具体的用例中:
基元如os_unfair_locks
and NSLocks
也很安全,但使用时需要小心。当用于紧密的、众所周知的临界区周围的数据同步时,在同步代码中使用锁是安全的。
因此,如果要对某些状态属性进行非常快速的线程安全同步,则可以使用锁(但要小心)。事实上,在 WWDC 2023 上超越结构化并发的基础知识 https://developer.apple.com/videos/play/wwdc2023/10170/?time=450,他们明确考虑了这种情况,使用final class
那是Sendable
:
虽然 Actor 对于保护封装状态非常有用,但我们希望修改和读取状态机上的各个属性,因此 Actor 并不是一个合适的工具。
此外,……我们可以使用调度队列或锁。
现在,在他们的示例中,他们将属性封装在ManagedAtomic https://github.com/apple/swift-atomics,所以他们不需要使用@unchecked
,但是如果您在可变变量周围使用锁,则可以这样指定。
例如,这里有一个@unchecked Sendable
使用锁的再现:
final class DemoPerson: @unchecked Sendable {
private var _phoneNumbers: Array<PhoneNumber> = []
private let lock = NSLock()
var phoneNumbers: Array<PhoneNumber> {
lock.withLock { _phoneNumbers }
}
@discardableResult
func removePhoneNumber(at index: Int) -> PhoneNumber {
lock.withLock { _phoneNumbers.remove(at: index) }
}
func addPhoneNumber(_ phoneNumber: PhoneNumber) {
lock.withLock { _phoneNumbers.append(phoneNumber) }
}
}
底线是,final class
那是@unchecked Sendable
可以使用锁。它有点脆弱,我们通常更喜欢提供编译时检查的 Swift 并发原语。但是,锁与 Swift 并发兼容(只要它仅限于“紧密的、众所周知的临界区”)。这就是提供的全部目的@unchecked Sendable
模式:它允许我们按照自己的节奏优雅地过渡到 Swift 并发。但是,我们有责任确保代码的线程安全。
一些小观察:
-
请注意,原始代码片段不是线程安全的,因为phoneNumber
计算属性未同步;和@unchecked Sendable
,我们失去了代码的编译时检查;我已经在上面解决了这个问题,但这很好地说明了为什么我们回避@unchecked
模式,因为它很容易让一些边缘情况从我们身边溜走而未被发现;
-
而不是手动lock
and unlock
,我们可能更喜欢withLock https://developer.apple.com/documentation/foundation/nslocking/4059821-withlock,保证加锁和解锁自动平衡;在这种情况下,逻辑非常琐碎,不那么重要,但如果您有更复杂的代码流程,手动锁定和解锁可能会变得更复杂且更脆弱;除了,withLock
减少代码片段中的一些语法噪音,使锁定的意图更加明确,等等。
一些不相关的观察结果:
-
我会让lock
一个不可变的(let
) 财产;
-
我更喜欢使用值类型(例如,SwiftArray
等)而不是NSMutableArray
…另外,虽然您可以使用NSMutableArray
,小心该数组中的对象......注意copy
仅执行数组的浅表复制,您很容易导致与该数组中的对象的意外共享和不同步交互;和
-
如果你需要NSObject
接口,请随意这样做,但通常我们不会在 Swift 代码库中这样做;我在上面的例子中删除了它,但是如果你需要NSObject
由于某些令人信服的原因而子类化,请随意这样做。