如何快速使“静态”变量成为线程安全的?

时间:2019-09-21 08:06:08

标签: ios swift thread-safety

class MyClass {
     static var name: String = "Hello"
}

默认情况下,swift中的静态变量不是线程安全的。如果要使它们成为线程安全的,该如何实现?

1 个答案:

答案 0 :(得分:3)

通过与静态变量进行同步,您可以像其他变量一样对线程进行静态更改。例如,您可以将您的公开属性设置为计算属性,该属性将同步对某些私有属性的访问。例如:

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串行队列或读写器进行同步。这个想法是一样的。


tl; dr

值得注意的是,这种属性级同步实现了线程安全性的低级概念,但这通常是不够的。通常,线程安全性是在更高的抽象级别上实现的。

考虑:

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

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

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。您不仅可以使两个属性中的每一个都具有线程安全性,而且还需要使更新两个属性的所有任务都成为线程安全。

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