Is static var thread-safe?
I have recently got this question in an interview.
Is the following code thread safe?
class Solution {
static var names: [Int] = []
}
Let’s analyse the code a bit to answer it:
- The
names
variable is defined as a static property in the Solution class. - Static properties are shared among all instances of the class and can be accessed and modified from multiple threads concurrently.
- Since there is no synchronization mechanism in place, concurrent access to the names array can lead to data races and inconsistent results.
- If multiple threads attempt to modify the
names
array simultaneously, it can result in unexpected behaviour, such as incorrect or incomplete modifications, data corruption, or crashes. - To ensure thread safety, concurrent access to the names array should be synchronized using appropriate mechanisms like locks, serial queues, or other synchronization primitives.
- Adding synchronization mechanisms will ensure that only one thread can access or modify the names array at a time, preventing data races and ensuring consistent behaviour.
Thread-safe solution
class ThreadSafeSolution {
private static var names: [Int] = []
private static let queue = DispatchQueue(label: "com.example.some-queue")
static var countNames: Int {
queue.sync {
names.count
}
}
static func addName(_ name: Int) {
queue.async(flags: .barrier) {
names.append(name)
}
}
}
Singleton
Extending our solution above to a real use case where we implement a SettingsManager, which manages a shared dictionary settings
. The synchronization mechanism ensures that multiple threads can read the value from a setting key, but only one thread can modify the setting at one time.
class SettingsManager {
static let shared = SettingsManager()
private let serialQueue = DispatchQueue(label: "com.example.SettingsManager.serialQueue")
private var settings: [String: Any] = [:]
private init() {}
func setValue(_ value: Any, forKey key: String) {
serialQueue.async(flags: .barrier) {
self.settings[key] = value
}
}
func value(forKey key: String) -> Any? {
var result: Any?
serialQueue.sync {
result = self.settings[key]
}
return result
}
}
Actor
The recent introduction of structured concurrency to Swift has greatly simplify synchronised accesses to shared mutable data. You can use a new actor type to define shared mutable data types. I highly recommend you to read more about Sendable, Actor and Structured Concurrency