Stateful Goroutines in Groovy

Our example demonstrates how to use goroutines and channels for state management in Groovy. While Groovy doesn’t have built-in concepts like goroutines or channels, we can achieve similar functionality using threads and concurrent data structures.

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingQueue

class ReadOp {
    int key
    BlockingQueue<Integer> resp = new LinkedBlockingQueue<>()
}

class WriteOp {
    int key
    int val
    BlockingQueue<Boolean> resp = new LinkedBlockingQueue<>()
}

def state = new ConcurrentHashMap<Integer, Integer>()
def readOps = new AtomicLong(0)
def writeOps = new AtomicLong(0)

def reads = new LinkedBlockingQueue<ReadOp>()
def writes = new LinkedBlockingQueue<WriteOp>()

// This thread manages the state
Thread.start {
    while (true) {
        if (!reads.isEmpty()) {
            def read = reads.take()
            read.resp.put(state.getOrDefault(read.key, 0))
        } else if (!writes.isEmpty()) {
            def write = writes.take()
            state.put(write.key, write.val)
            write.resp.put(true)
        }
    }
}

// Start 100 read threads
100.times {
    Thread.start {
        while (true) {
            def read = new ReadOp(key: new Random().nextInt(5))
            reads.put(read)
            read.resp.take()
            readOps.incrementAndGet()
            Thread.sleep(1)
        }
    }
}

// Start 10 write threads
10.times {
    Thread.start {
        while (true) {
            def write = new WriteOp(key: new Random().nextInt(5), val: new Random().nextInt(100))
            writes.put(write)
            write.resp.take()
            writeOps.incrementAndGet()
            Thread.sleep(1)
        }
    }
}

// Let the threads work for a second
Thread.sleep(1000)

// Report the operation counts
println "readOps: ${readOps.get()}"
println "writeOps: ${writeOps.get()}"

In this Groovy example, we use threads to simulate goroutines and LinkedBlockingQueue to simulate channels. The ConcurrentHashMap is used as a thread-safe alternative to the map in the original Go example.

We create a main thread that manages the state, responding to read and write requests from other threads. We then start 100 read threads and 10 write threads that continuously send read and write operations to the main thread.

The AtomicLong class is used to keep track of the number of read and write operations in a thread-safe manner.

After letting the threads run for a second, we print out the total number of read and write operations performed.

Running this program will show the number of read and write operations completed in one second:

$ groovy stateful_threads.groovy
readOps: 71708
writeOps: 7177

This Groovy-based approach using threads and concurrent data structures provides similar functionality to the original Go example with goroutines and channels. While the implementation details differ, the overall concept of managing shared state through message passing remains the same.

Remember to choose the approach that best fits your specific use case and makes your program’s correctness easier to reason about.