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
NSLockfor mutual exclusion, which is similar to Go’ssync.Mutex.The
Containeris 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
DispatchGroupandDispatchQueuefor concurrency, which is Swift’s equivalent to Go’s goroutines andWaitGroup.The
incmethod is implemented within theContainerclass, and we’ve added agetCountersmethod to safely access the counters.We use Swift’s
deferstatement to ensure the lock is always unlocked, similar to the Go version.Instead of goroutines, we use
DispatchQueue.global().asyncto 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.