Mutexes in Swift
import Foundation
// Container holds a dictionary of counters; since we want to
// update it concurrently from multiple threads, we
// add a lock to synchronize access.
// Note that locks must not be copied, so if this
// class is passed around, it should be done by
// reference.
class Container {
private var lock = NSLock()
private var counters: [String: Int]
init(counters: [String: Int]) {
self.counters = counters
}
// Lock before accessing `counters`; unlock
// it at the end of the function.
func inc(name: String) {
lock.lock()
defer { lock.unlock() }
counters[name, default: 0] += 1
}
func getCounters() -> [String: Int] {
lock.lock()
defer { lock.unlock() }
return counters
}
}
func main() {
// Note that the default value of a lock is usable as-is, so no
// initialization is required here.
let c = Container(counters: ["a": 0, "b": 0])
let group = DispatchGroup()
// This function increments a named counter
// in a loop.
func doIncrement(name: String, n: Int) {
for _ in 0..<n {
c.inc(name: name)
}
group.leave()
}
// Run several threads concurrently; note
// that they all access the same `Container`,
// and two of them access the same counter.
group.enter()
DispatchQueue.global().async {
doIncrement(name: "a", n: 10000)
}
group.enter()
DispatchQueue.global().async {
doIncrement(name: "a", n: 10000)
}
group.enter()
DispatchQueue.global().async {
doIncrement(name: "b", n: 10000)
}
// Wait for the threads to finish
group.wait()
print(c.getCounters())
}
main()
In this Swift version, we’ve made the following changes:
We use
NSLock
for mutual exclusion, which is similar to Go’ssync.Mutex
.The
Container
is implemented as a class, which is reference-typed in Swift, similar to how the Go version uses a pointer to the struct.We use a
DispatchGroup
andDispatchQueue
for concurrency, which is Swift’s equivalent to Go’s goroutines andWaitGroup
.The
inc
method is implemented within theContainer
class, and we’ve added agetCounters
method to safely access the counters.We use Swift’s
defer
statement to ensure the lock is always unlocked, similar to the Go version.Instead of goroutines, we use
DispatchQueue.global().async
to run tasks concurrently.
Running this program should show that the counters are updated as expected:
["a": 20000, "b": 10000]
This example demonstrates how to use locks in Swift to safely access shared data across multiple threads. The NSLock
ensures that only one thread can access the counters
dictionary at a time, preventing race conditions.
Next, we could look at implementing this same state management task using only Grand Central Dispatch and Swift’s built-in concurrency features.