Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can we make 'static' variables Thread-Safe in swift?

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

Static variables in swift are not thread-safe by default. If I want to make them thread-safe, how can I achieve that ?

like image 894
nikksindia Avatar asked Sep 21 '19 08:09

nikksindia


1 Answers

Initialization of static variable is thread-safe. But if the object, itself, is not thread-safe, must synchronize your interaction with it from multiple threads (as you must with any non-thread-safe object, whether static or not).

At the bare minimum, you can make your exposed property a computed property that synchronizes access to some private property. For example:

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

Where

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

Or you can use GCD serial queue, reader-writer, or a variety of other mechanisms to synchronize, too. The basic idea would be the same, though.

That having been said, it’s worth noting that this sort of property accessor synchronization is insufficient for mutable types. A higher level of synchronization is needed.

Consider:

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

You’d think that because we have thread-safe accessors that everything is OK. But it’s not. This will not add 200,000 characters to the name. You’d have to do something like:

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

And then the following works:

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

The other classic example is where you have two properties that related to each other, for example, maybe firstName and lastName. You cannot just make each of the two properties thread-safe, but rather you need to make the single task of updating both properties thread-safe.

These are silly examples, but illustrate that sometimes a higher level of abstraction is needed. But for simple applications, the synchronizing the computed properties’ accessor methods may be sufficient.


As a point of clarification, while statics, like globals, are instantiated lazily, standard stored properties bearing the lazy qualifier are not thread-safe. As The Swift Programming Language: Properties warns us:

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property hasn’t yet been initialized, there’s no guarantee that the property will be initialized only once.

like image 101
Rob Avatar answered Nov 15 '22 07:11

Rob