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.