比方说,有一个变量是我想让线程安全的。最常见的方法之一是:
var value: A {
get { return queue.sync { self._value } }
set { queue.sync { self._value = newValue } }
}但是,如果我们如下面的示例所示更改该值,则为此属性不是完全线程安全的。:
Class.value += 1因此,我的问题是:在相同的原则上使用NSLock,也不是完全线程安全?。
var value: A {
get {
lock.lock()
defer { lock.unlock() }
return self._value
}
set {
lock.lock()
defer { lock.unlock() }
self._value = newValue
}
}发布于 2020-04-30 16:34:25
很有趣,我是第一次学到这个。
第一段代码中的问题是:
object.value += 1
具有与
object.value = object.value + 1
我们可以进一步扩展到:
let originalValue = queue.sync { object._value }
let newValue = origiinalValue + 1
queue.sync { self._value = newValue }扩展它可以清楚地表明getter和setter的同步很好,但是它们不是作为一个整体同步的。上面代码中间的上下文开关可能导致_value被另一个线程更改,而不需要newValue来反映更改。
使用锁也会有同样的问题。它将扩大到:
lock.lock()
let originalValue = object._value
lock.unlock()
let newValue = originalValue + 1
lock.lock()
object._value = newValue
lock.unlock()通过使用一些日志语句对代码进行检测,您可以看到这一点,这些语句表明,锁并没有完全覆盖这种变异:
class C {
var lock = NSLock()
var _value: Int
var value: Int {
get {
print("value.get start")
print("lock.lock()")
lock.lock()
defer {
print("lock.unlock()")
lock.unlock()
print("value.get end")
}
print("getting self._value")
return self._value
}
set {
print("\n\n\nvalue.set start")
lock.lock()
print("lock.lock()")
defer {
print("lock.unlock()")
lock.unlock()
print("value.set end")
}
print("setting self._value")
self._value = newValue
}
}
init(_ value: Int) { self._value = value }
}
let object = C(0)
object.value += 1发布于 2021-06-29 21:44:03
在回答您的问题时,锁方法遇到了与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等),这种模式都是适用的。
就其价值而言,SWIFT5.5 actor模式(在硒-0306中概述)将此模式形式化。考虑:
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将控制它的属性与setter的交互,也就是说,如果您试图从这个类之外设置value,您将收到一个错误:
参与者-孤立的属性“值”只能从参与者内部发生变异。
https://stackoverflow.com/questions/61528184
复制相似问题