Mutexes in Chapel

In the previous example, we saw how to manage simple counter state using atomic operations. For more complex state, we can use a lock to safely access data across multiple tasks.

use Time;
use Map;

// Container holds a map of counters; since we want to
// update it concurrently from multiple tasks, we
// add a lock to synchronize access.
class Container {
    var counterLock: sync bool;
    var counters: map(string, int);

    // Lock before accessing counters; unlock
    // at the end of the function.
    proc inc(name: string) {
        counterLock.writeEF(true);
        counters[name] += 1;
        counterLock.readFE();
    }
}

// This function increments a named counter
// in a loop.
proc doIncrement(c: Container, name: string, n: int) {
    for i in 1..n {
        c.inc(name);
    }
}

proc main() {
    var c = new Container();
    c.counters = new map(string, int);
    c.counters["a"] = 0;
    c.counters["b"] = 0;

    // Run several tasks concurrently; note
    // that they all access the same Container,
    // and two of them access the same counter.
    sync {
        begin doIncrement(c, "a", 10000);
        begin doIncrement(c, "a", 10000);
        begin doIncrement(c, "b", 10000);
    }

    writeln(c.counters);
}

Running the program shows that the counters updated as expected.

$ chpl mutexes.chpl
$ ./mutexes
{"a": 20000, "b": 10000}

In this Chapel version:

  1. We use a sync variable counterLock instead of a mutex to provide synchronization.
  2. The Container class holds the lock and the map of counters.
  3. The inc method uses the sync variable to provide mutual exclusion.
  4. We use Chapel’s begin statement to spawn concurrent tasks, similar to goroutines in Go.
  5. The sync block in main is used to wait for all tasks to complete, similar to WaitGroup in Go.
  6. Chapel’s map type is used instead of Go’s map.

This example demonstrates how to use synchronization primitives in Chapel to safely manage shared state across multiple concurrent tasks.