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 ?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With