Stateful Goroutines in Swift

Our first example demonstrates stateful goroutines using Swift’s concurrency features. This approach aligns with Swift’s ideas of sharing memory by communicating and having each piece of data owned by exactly 1 actor.

import Foundation

// These structs encapsulate read and write requests and provide a way for the owning actor to respond.
struct ReadOp {
    let key: Int
    let respond: (Int) -> Void
}

struct WriteOp {
    let key: Int
    let val: Int
    let respond: (Bool) -> Void
}

actor StateManager {
    private var state = [Int: Int]()
    
    func read(_ key: Int) -> Int {
        return state[key, default: 0]
    }
    
    func write(key: Int, value: Int) {
        state[key] = value
    }
}

@main
struct StatefulConcurrencyExample {
    static func main() async {
        let stateManager = StateManager()
        var readOps = 0
        var writeOps = 0
        
        // Start 100 tasks to perform reads
        for _ in 0..<100 {
            Task {
                while true {
                    let key = Int.random(in: 0..<5)
                    let _ = await stateManager.read(key)
                    readOps += 1
                    try? await Task.sleep(nanoseconds: 1_000_000) // Sleep for 1ms
                }
            }
        }
        
        // Start 10 tasks to perform writes
        for _ in 0..<10 {
            Task {
                while true {
                    let key = Int.random(in: 0..<5)
                    let value = Int.random(in: 0..<100)
                    await stateManager.write(key: key, value: value)
                    writeOps += 1
                    try? await Task.sleep(nanoseconds: 1_000_000) // Sleep for 1ms
                }
            }
        }
        
        // Let the tasks work for a second
        try? await Task.sleep(nanoseconds: 1_000_000_000)
        
        // Report the operation counts
        print("readOps:", readOps)
        print("writeOps:", writeOps)
    }
}

Running our program shows that the actor-based state management example completes a certain number of total operations:

$ swift run
readOps: 71708
writeOps: 7177

For this particular case, the actor-based approach in Swift provides a safe and efficient way to manage shared state. It eliminates the need for explicit locking while still ensuring thread-safe access to the shared data. This approach aligns well with Swift’s concurrency model, making it easier to reason about the correctness of your program.

Actors in Swift serve a similar purpose to goroutines in Go for this example. They provide a way to encapsulate state and ensure that it’s accessed in a thread-safe manner. The @main attribute and static func main() are used as the entry point for the Swift program, similar to the main() function in Go.

Remember to use whichever approach feels most natural and makes it easiest to understand and ensure the correctness of your program.