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:

  1. We use NSLock for mutual exclusion, which is similar to Go’s sync.Mutex.

  2. 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.

  3. We use a DispatchGroup and DispatchQueue for concurrency, which is Swift’s equivalent to Go’s goroutines and WaitGroup.

  4. The inc method is implemented within the Container class, and we’ve added a getCounters method to safely access the counters.

  5. We use Swift’s defer statement to ensure the lock is always unlocked, similar to the Go version.

  6. 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.