在回答您的问题时,锁定方法遇到的问题与 GCD 方法完全相同。原子访问器方法根本不足以确保更广泛的线程安全。
正如其他地方所讨论的那样,问题在于无害+=
运算符通过 getter 检索值,增加该值,并通过 setter 存储该新值。为了实现线程安全,整个过程需要包装在一个同步机制中。你想要一个原子增量操作,你会写一个方法来做到这一点。
因此,以您NSLock
为例,我可能会将同步逻辑移至其自己的方法中,例如:
class Foo<T> {
private let lock = NSLock()
private var _value: T
init(value: T) {
_value = value
}
var value: T {
get { lock.synchronized { _value } }
set { lock.synchronized { _value = newValue } }
}
}
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
但是如果你想要一个操作以线程安全的方式增加值,你可以编写一个方法来做到这一点,例如:
extension Foo where T: Numeric {
func increment(by increment: T) {
lock.synchronized {
_value += increment
}
}
}
然后,而不是这种非线程安全的尝试:
foo.value += 1
您将改为使用以下线程安全的再现:
foo.increment(by: 1)
这种模式,将增量过程包装在它自己的同步整个操作的方法中,无论您使用什么同步机制(例如,锁、GCD 串行队列、读写器模式os_unfair_lock
等),都将适用。
对于它的价值,Swift 5.5模式(在SE-0306actor
中概述)正式化了这种模式。考虑:
actor Bar<T> {
var value: T
init(value: T) {
self.value = value
}
}
extension Bar where T: Numeric {
func increment(by increment: T) {
value += increment
}
}
在这里,该increment
方法自动是一个“actor-isolated”方法(即,它将被同步),但actor
将控制与其属性的设置器的交互,即如果您尝试value
从此类外部设置,您将收到错误消息:
演员隔离的属性“值”只能从演员内部变异