Использование NSLock в установщике свойств - PullRequest
0 голосов
/ 30 апреля 2020

Допустим, есть переменная, которую я хочу сделать потокобезопасной. Один из самых распространенных способов сделать это:

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
    }
}

1 Ответ

1 голос
/ 30 апреля 2020

Это интересно, я узнаю об этом впервые.

Проблема в первом бите кода заключается в том, что:

object.value += 1

имеет ту же семантику, что и

object.value = object.value + 1

, которую мы можем расширить до:

let originalValue = queue.sync { object._value }
let newValue = origiinalValue + 1
queue.sync { self._value = newValue }

Расширяя ее, вы понимаете, что синхронизация геттера и сеттера работают нормально, но они не синхронизированы в целом. Переключение контекста в середине кода выше может привести к мутации _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
...