Atomic Counters in F#
Our primary mechanism for managing state in F# is typically through immutable data structures and functional programming patterns. However, there are scenarios where we need to manage shared mutable state across multiple threads. In this example, we’ll look at using atomic operations for managing a counter accessed by multiple threads.
open System
open System.Threading
open System.Threading.Tasks
let main () =
// We'll use an atomic integer to represent our (always-positive) counter.
let ops = ref 0L
// We'll start 50 tasks that each increment the counter exactly 1000 times.
let tasks =
[1..50]
|> List.map (fun _ ->
Task.Run(fun () ->
for _ in 1..1000 do
// To atomically increment the counter we use Interlocked.Increment
Interlocked.Increment(ops) |> ignore
)
)
// Wait until all the tasks are done.
Task.WaitAll(tasks |> Array.ofList)
// It's safe to read the value of 'ops' here as all tasks have completed
printfn "ops: %d" !ops
main ()
In this F# version:
We use
ref
to create a mutable reference cell for our counter, initialized to 0L (long integer).Instead of goroutines, we use F#’s
Task.Run
to create and run tasks concurrently.We use
Interlocked.Increment
for atomic incrementation of the counter. This is equivalent to theAdd
method used in the original example.We use
Task.WaitAll
to wait for all tasks to complete, which is similar to theWaitGroup.Wait()
in the original.After all tasks are complete, we can safely read the value of
ops
using the!
operator to dereference the ref cell.
When you run this program, you should see:
ops: 50000
We expect to get exactly 50,000 operations. If we had used a non-atomic integer and incremented it with normal addition, we’d likely get a different number, changing between runs, because the tasks would interfere with each other.
This example demonstrates how to use atomic operations in F# for safe concurrent access to shared mutable state. However, in idiomatic F# programming, you would typically prefer immutable data structures and message-passing concurrency models when possible.