Mutexes in Nim

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 locks
import tables

# Container holds a map of counters; since we want to
# update it concurrently from multiple threads, we
# add a `Lock` to synchronize access.
# Note that locks must not be copied, so if this
# `object` is passed around, it should be done by
# pointer.
type
  Container = object
    lock: Lock
    counters: Table[string, int]

# Lock the mutex before accessing `counters`; unlock
# it at the end of the function using a `defer`
# statement.
proc inc(c: var Container, name: string) =
  c.lock.acquire()
  defer: c.lock.release()
  c.counters[name] = c.counters.getOrDefault(name) + 1

# Note that we need to initialize the lock before use
proc newContainer(): Container =
  result = Container(counters: initTable[string, int]())
  initLock(result.lock)

proc main() =
  var c = newContainer()
  c.counters["a"] = 0
  c.counters["b"] = 0

  # This function increments a named counter
  # in a loop.
  proc doIncrement(name: string, n: int) {.thread.} =
    for i in 0..<n:
      c.inc(name)

  var threads: array[3, Thread[tuple[name: string, n: int]]]
  
  # Run several threads concurrently; note
  # that they all access the same `Container`,
  # and two of them access the same counter.
  createThread(threads[0], doIncrement, ("a", 10000))
  createThread(threads[1], doIncrement, ("a", 10000))
  createThread(threads[2], doIncrement, ("b", 10000))

  # Wait for the threads to finish
  joinThreads(threads)

  echo c.counters

main()

Running the program shows that the counters updated as expected.

$ nim c -r mutexes.nim
{a: 20000, b: 10000}

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

Note: In Nim, we use Lock from the locks module instead of Mutex. The concept is similar, but the syntax is slightly different. Also, Nim uses threads instead of goroutines, so we’ve adjusted the code accordingly. The defer statement in Nim works similarly to Go’s, ensuring the lock is released at the end of the function.