Atomic Counters in Elixir
In Elixir, we can use the :atomics module to work with atomic counters. Here’s how we can implement atomic counters:
defmodule AtomicCounters do
  def main do
    # We'll use an atomic reference to represent our (always-positive) counter.
    {:ok, counter} = :atomics.new(1, signed: false)
    # We'll start 50 tasks that each increment the counter exactly 1000 times.
    tasks = for _ <- 1..50 do
      Task.async(fn ->
        for _ <- 1..1000 do
          # To atomically increment the counter we use :atomics.add/3
          :atomics.add(counter, 1, 1)
        end
      end)
    end
    # Wait until all the tasks are done.
    Task.await_many(tasks)
    # Here no tasks are writing to the counter, but using :atomics.get/2
    # it's safe to atomically read a value even while other tasks are
    # (atomically) updating it.
    IO.puts("ops: #{:atomics.get(counter, 1)}")
  end
end
AtomicCounters.main()In this Elixir implementation:
- We use - :atomics.new/2to create a new atomic counter. The first argument- 1specifies that we want a single counter, and- signed: falseindicates that it’s an unsigned integer.
- Instead of goroutines, we use Elixir’s - Taskmodule to create concurrent tasks.
- We use - :atomics.add/3to atomically increment the counter. The first argument is the atomic reference, the second is the index (1 in this case as we only have one counter), and the third is the value to add.
- We use - Task.await_many/1to wait for all tasks to complete, which is similar to- WaitGroup.Wait()in the original example.
- Finally, we use - :atomics.get/2to safely read the final value of the counter.
To run this program:
$ elixir atomic_counters.exs
ops: 50000We expect to get exactly 50,000 operations. If we had used a non-atomic integer and incremented it with regular addition, we’d likely get a different number, changing between runs, because the tasks would interfere with each other.
Elixir’s :atomics module provides thread-safe operations on individual numbers, making it suitable for scenarios where you need to share counters or flags between multiple processes without explicit locking.