簡體   English   中英

NSLock 在屬性設置器中的使用

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM