Atomic Counters in Nim

Our primary mechanism for managing state in Nim is communication over channels. However, there are a few other options for managing state. Here we’ll look at using atomic operations for atomic counters accessed by multiple threads.

import atomics
import std/threadpool

var ops = Atomic[uint64](0)

proc incrementCounter() =
  for _ in 0..<1000:
    ops.atomicInc()

proc main() =
  var threads: array[50, Thread[void]]
  
  for i in 0..<50:
    createThread(threads[i], incrementCounter)
  
  joinThreads(threads)
  
  echo "ops: ", load(ops)

main()

We use an atomic integer type to represent our (always-positive) counter.

We’ll start 50 threads that each increment the counter exactly 1000 times.

To atomically increment the counter we use atomicInc.

After all threads are done, we use load to safely read the final value of the atomic counter.

We expect to get exactly 50,000 operations. Had we used a non-atomic integer and incremented it with regular addition, we’d likely get a different number, changing between runs, because the threads would interfere with each other.

To run the program:

$ nim c -r atomic_counters.nim
ops: 50000

Next, we’ll look at mutexes, another tool for managing state.