Mutexes in Wolfram Language

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

(* Container holds an association of counters; since we want to
   update it concurrently from multiple threads, we
   add a SynchronizedAssociation to synchronize access. *)
container = SynchronizedAssociation[<|"a" -> 0, "b" -> 0|>];

(* This function increments a named counter *)
inc[name_] := container[name] += 1;

(* This function increments a named counter in a loop *)
doIncrement[name_, n_] := Table[inc[name], {n}];

(* Run several ParallelSubmit tasks concurrently; note
   that they all access the same container,
   and two of them access the same counter. *)
results = ParallelSubmit /@ {
  doIncrement["a", 10000],
  doIncrement["a", 10000],
  doIncrement["b", 10000]
};

(* Wait for all tasks to finish *)
WaitAll[results];

(* Print the final state of the counters *)
Print[Normal[container]]

Running the program shows that the counters updated as expected:

<|"a" -> 20000, "b" -> 10000|>

In this Wolfram Language version:

  1. We use SynchronizedAssociation instead of a custom struct with a mutex. This provides thread-safe access to the counters.

  2. The inc function is simplified to directly increment the counter in the SynchronizedAssociation.

  3. Instead of goroutines, we use ParallelSubmit to run tasks concurrently. These are similar to threads in other languages.

  4. We use WaitAll to wait for all parallel tasks to complete, which is analogous to WaitGroup.Wait() in the original code.

  5. Finally, we use Normal to convert the SynchronizedAssociation to a regular association for printing.

This implementation achieves the same goal of safely incrementing counters from multiple concurrent tasks, adapting the concepts to Wolfram Language’s built-in concurrency primitives.