【问题标题】:How can we make 'static' variables Thread-Safe in swift?我们如何快速使“静态”变量线程安全?
【发布时间】:2019-09-21 08:06:08
【问题描述】:
class MyClass {
     static var name: String = "Hello"
}

swift 中的静态变量默认不是线程安全的。如果我想让它们成为线程安全的,我该如何实现呢?

【问题讨论】:

  • 是的,我在这里说的是变异变量

标签: ios swift thread-safety


【解决方案1】:

static 变量的初始化线程安全的。但是,如果对象本身不是线程安全的,则必须从多个线程同步您与它的交互(就像您必须与任何非线程安全的对象一样,无论static 与否)。

至少,您可以使您的公开属性成为同步访问某些私有属性的计算属性。例如:

class MyClass {
    private static let lock = NSLock()
    private static var _name: String = "Hello"

    static var name: String {
        get { lock.withCriticalSection { _name } }
        set { lock.withCriticalSection { _name = newValue } }
    }
}

在哪里

extension NSLocking {
    func withCriticalSection<T>(block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

或者您也可以使用 GCD 串行队列、读写器或各种其他机制进行同步。不过,基本的想法是一样的。

话虽如此,但值得注意的是,这种属性访问器同步对于可变类型来说是不够的。需要更高级别的同步。

考虑:

let group = DispatchGroup()

DispatchQueue.global().async(group: group) {
    for _ in 0 ..< 100_000 {
        MyClass.name += "x"
    }
}

DispatchQueue.global().async(group: group) {
    for _ in 0 ..< 100_000 {
        MyClass.name += "y"
    }
}

group.notify(queue: .main) {
    print(MyClass.name.count)
}

你会认为因为我们有线程安全的访问器,所以一切都很好。但事实并非如此。这不会将 200,000 个字符添加到 name。您必须执行以下操作:

class MyClass {
    private static let lock = NSLock()
    private static var _name: String = ""

    static var name: String {
        get { lock.withCriticalSection { _name } }
    }

    static func appendString(_ string: String) {
        lock.withCriticalSection {
            _name += string
        }
    }
}

然后以下工作:

let group = DispatchGroup()

DispatchQueue.global().async(group: group) {
    for _ in 0 ..< 100_000 {
        MyClass.appendString("x")
    }
}

DispatchQueue.global().async(group: group) {
    for _ in 0 ..< 100_000 {
        MyClass.appendString("y")
    }
}

group.notify(queue: .main) {
    print(MyClass.name.count)
}

另一个经典示例是您有两个相互关联的属性,例如,可能是firstNamelastName。您不能只使这两个属性中的每一个都是线程安全的,而是需要使更新这两个属性的单个任务成为线程安全的。

这些都是愚蠢的例子,但说明有时需要更高级别的抽象。但对于简单的应用程序,同步计算属性的访问器方法可能就足够了。


澄清一点,虽然静态变量(如全局变量)是延迟实例化的,但带有 lazy 限定符的标准存储属性不是线程安全的。正如The Swift Programming Language: Properties 警告我们的那样:

如果一个带有lazy修饰符的属性被多个线程同时访问,并且该属性尚未初始化,则不能保证该属性只会被初始化一次。

【讨论】:

  • NSLock 的使用能否确保在 CPU 级别应用适当的内存屏障指令?确保您的书面值尽快对其他线程可见。
  • 锁是一种成熟的synchronization mechanism,用于编写线程安全的代码。而且它们的性能很高,在我最近的基准测试中比 GCD 更快,但不如操作系统不公平锁那么快。 (而且,FWIW,绝大多数情况下根本无法观察到这些差异;避免过早优化。)
  • 太棒了。这是一个非常棒的策略。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-18
  • 2011-04-05
  • 1970-01-01
  • 2014-12-24
相关资源
最近更新 更多