[英]Usage of NSLock in property setter
假設有一個變量我想讓線程安全。 執行此操作的最常見方法之一:
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
}
}
在回答您的問題時,鎖定方法遇到的問題與 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 actor
模式(在SE-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-isolated”方法(即,它將被同步),但actor
將控制與其屬性的設置器的交互,即如果您嘗試從該 class 外部設置value
,您將收到一個錯誤:
演員隔離的屬性“值”只能從演員內部變異
這很有趣,我是第一次學習這個。
第一段代碼的問題是:
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.