Mutexes in F#

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

open System
open System.Threading
open System.Collections.Generic

// Container holds a dictionary of counters; since we want to
// update it concurrently from multiple threads, we
// add a lock object to synchronize access.
type Container() =
    let mutable counters = Dictionary<string, int>()
    let lockObj = new Object()

    // Lock the object before accessing counters; unlock
    // it at the end of the function using a try-finally block.
    member this.Inc(name: string) =
        lock lockObj (fun () ->
            match counters.TryGetValue(name) with
            | true, value -> counters.[name] <- value + 1
            | false, _ -> counters.[name] <- 1
        )

    member this.GetCounters() = counters

// Note that we initialize the Container with some initial values
let c = Container()
c.Inc("a")
c.Inc("b")

// This function increments a named counter in a loop.
let doIncrement (name: string) (n: int) =
    for _ in 1 .. n do
        c.Inc(name)

// Run several threads concurrently; note
// that they all access the same Container,
// and two of them access the same counter.
let threads = 
    [| 
        Thread(ThreadStart(fun () -> doIncrement "a" 10000))
        Thread(ThreadStart(fun () -> doIncrement "a" 10000))
        Thread(ThreadStart(fun () -> doIncrement "b" 10000))
    |]

// Start all threads
Array.iter (fun (t: Thread) -> t.Start()) threads

// Wait for the threads to finish
Array.iter (fun (t: Thread) -> t.Join()) threads

// Print the final state of the counters
for KeyValue(key, value) in c.GetCounters() do
    printfn "%s: %d" key value

Running the program shows that the counters updated as expected.

$ dotnet fsi mutexes.fsx
a: 20000
b: 10000

Next, we’ll look at implementing this same state management task using only asynchronous workflows and mailbox processors.