0

假设有一个变量我想让线程安全。执行此操作的最常见方法之一:

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

2 回答 2

2

在回答您的问题时,锁定方法遇到的问题与 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从此类外部设置,您将收到错误消息:

演员隔离的属性“值”只能从演员内部变异

于 2021-06-29T21:44:03.163 回答
1

这很有趣,我是第一次学习这个。

第一段代码的问题是:

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
于 2020-04-30T16:34:25.393 回答