Evolving Thread-Safe Swift Code: From Grand Central Dispatch to Concurrency

In the past, thread-safe code in Swift often relied on Grand Central Dispatch (GCD). Let's explore an example:
import Foundation
class ReadWriteLock {
private var concurrentQueue = DispatchQueue(label: "com.example.readWriteLock", attributes: .concurrent)
private var value: Int = 0
func read() -> Int {
var result: Int = 0
concurrentQueue.sync {
result = value
}
return result
}
func write(_ newValue: Int) {
concurrentQueue.async(flags: .barrier) {
self.value = newValue
}
}
}
In this example, the ReadWriteLock class offers two methods: read() and write(_:).
The read() method retrieves the current value of the value property in a thread-safe manner using synchronous dispatch.
The write(_:) method updates the value property with a new value, also in a thread-safe manner, using asynchronous dispatch with the .barrier flag. This ensures exclusive access during write operations, preventing race conditions while allowing multiple readers simultaneous access.
With the introduction of Swift 5.5, concurrency brought forth a simpler approach to writing thread-safe code. Let's re-imagine the previous example using concurrency:
import Foundation
actor ReadWriteActor {
private var value: Int = 0
func read() -> Int {
return value
}
func write(_ newValue: Int) {
value = newValue
}
}
In this version, ReadWriteActor is declared as an actor. Actors ensure state isolation, allowing only one task to access the actor's mutable state at a time. This eliminates the need for manual synchronization mechanisms like locks or GCD queues.
The read() method simply returns the current value of the value property, while the write(_:) method updates the value property with a new value. Access to the value property is automatically serialized by the actor, ensuring thread safety without the need for explicit locking or synchronization.
[Originally posted in https://isometimesdev.blogspot.com/2024/06/evolving-thread-safe-swift-code-from.html on June 10, 2024]




Comments