Stateful Goroutines in Chapel
In this example, we'll explore how to manage state using Chapel's task parallelism and synchronization features. This approach aligns with Chapel's ideas of parallel computing and shared memory programming.
```chapel
use Random;
use Time;
use Atomics;
// Define record types for read and write operations
record readOp {
var key: int;
var resp: sync int;
}
record writeOp {
var key: int;
var val: int;
var resp: sync bool;
}
proc main() {
// Count operations
var readOps: atomic uint;
var writeOps: atomic uint;
// Channels for read and write requests
var reads = new channel(readOp);
var writes = new channel(writeOp);
// Task that owns the state
begin {
var state: [0..4] int;
while true {
select {
when var read = reads.recv() {
read.resp.write(state[read.key]);
}
when var write = writes.recv() {
state[write.key] = write.val;
write.resp.write(true);
}
}
}
}
// Start 100 reader tasks
coforall _ in 1..100 {
while true {
var read = new readOp(key: randInt(0, 4), resp: new sync int);
reads.send(read);
read.resp.readFE();
readOps.add(1);
Time.sleep(1);
}
}
// Start 10 writer tasks
coforall _ in 1..10 {
while true {
var write = new writeOp(key: randInt(0, 4), val: randInt(0, 99), resp: new sync bool);
writes.send(write);
write.resp.readFE();
writeOps.add(1);
Time.sleep(1);
}
}
// Let the tasks work for a second
Time.sleep(1000);
// Report the operation counts
writeln("readOps: ", readOps.read());
writeln("writeOps: ", writeOps.read());
}
In this Chapel version, we use tasks (similar to goroutines) and channels to manage shared state. The sync
variables are used for synchronization between tasks.
We define readOp
and writeOp
records to encapsulate the requests and responses. The main task creates channels for reads and writes, and starts a task that owns the state (implemented as an array in this case).
We then start 100 reader tasks and 10 writer tasks using coforall
loops. Each task sends requests through the appropriate channel and waits for a response using sync
variables.
The state-owning task uses a select
statement to handle incoming requests, similar to the Go version.
To run the program, save it as stateful_tasks.chpl
and use the Chapel compiler:
$ chpl stateful_tasks.chpl -o stateful_tasks
$ ./stateful_tasks
readOps: 71708
writeOps: 7177
The output shows that the task-based state management example completes about 80,000 total operations in one second.
This approach demonstrates Chapel’s parallel computing capabilities. While it might be more complex than using explicit locks, it can be beneficial in scenarios involving multiple synchronization points or when managing multiple locks would be error-prone. Choose the approach that feels most natural and helps ensure the correctness of your program.