Mutexes in D Programming Language

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

import std.stdio;
import core.sync.mutex;
import std.concurrency;

// Container holds a map of counters; since we want to
// update it concurrently from multiple threads, we
// add a Mutex to synchronize access.
// Note that mutexes must not be copied, so if this
// struct is passed around, it should be done by
// pointer.
class Container {
    private Mutex mu;
    private int[string] counters;

    this() {
        mu = new Mutex();
        counters = ["a": 0, "b": 0];
    }

    // Lock the mutex before accessing counters; unlock
    // it at the end of the function using a scope(exit) statement.
    void inc(string name) {
        synchronized(mu) {
            counters[name]++;
        }
    }
}

void main() {
    auto c = new Container();

    // This function increments a named counter
    // in a loop.
    void doIncrement(string name, int n) {
        for (int i = 0; i < n; i++) {
            c.inc(name);
        }
    }

    // Run several threads concurrently; note
    // that they all access the same Container,
    // and two of them access the same counter.
    auto t1 = spawn(&doIncrement, "a", 10000);
    auto t2 = spawn(&doIncrement, "a", 10000);
    auto t3 = spawn(&doIncrement, "b", 10000);

    // Wait for the threads to finish
    thread_joinAll();

    writeln(c.counters);
}

Running the program shows that the counters updated as expected.

$ dmd -run mutexes.d
["a": 20000, "b": 10000]

Next we’ll look at implementing this same state management task using only threads and message passing.

In this D version:

  1. We use core.sync.mutex.Mutex for mutual exclusion.
  2. Instead of goroutines, we use D’s std.concurrency module to spawn threads.
  3. The Container is implemented as a class instead of a struct.
  4. We use synchronized(mu) instead of explicitly calling Lock() and Unlock().
  5. D doesn’t have a built-in WaitGroup, so we use thread_joinAll() to wait for all threads to complete.
  6. The doIncrement function is defined inside main() to have access to the Container instance.

This example demonstrates how to use mutexes in D to safely access shared data from multiple threads, achieving the same functionality as the original code.