Mutexes in Haskell
In the previous example, we saw how to manage simple counter state using atomic operations. For more complex state, we can use a mutex to safely access data across multiple threads.
import Control.Concurrent
import Control.Concurrent.MVar
import qualified Data.Map.Strict as Map
import Control.Monad (replicateM_)
data Container = Container
{ counters :: MVar (Map.Map String Int)
}
newContainer :: IO Container
newContainer = do
countersMVar <- newMVar $ Map.fromList [("a", 0), ("b", 0)]
return $ Container countersMVar
inc :: Container -> String -> IO ()
inc container name = modifyMVar_ (counters container) $ \m ->
return $ Map.insertWith (+) name 1 m
doIncrement :: Container -> String -> Int -> IO ()
doIncrement container name n = replicateM_ n $ inc container name
main :: IO ()
main = do
c <- newContainer
-- Run several threads concurrently; note
-- that they all access the same Container,
-- and two of them access the same counter.
threads <- mapM forkIO
[ doIncrement c "a" 10000
, doIncrement c "a" 10000
, doIncrement c "b" 10000
]
-- Wait for the threads to finish
mapM_ takeMVar threads
-- Print the final state of the counters
finalCounters <- readMVar (counters c)
print finalCounters
In this Haskell version, we use MVar
to implement mutual exclusion. The Container
type holds an MVar
containing a Map
of counters. The inc
function uses modifyMVar_
to safely update the counter values.
Note that in Haskell, we don’t need to explicitly unlock the mutex, as modifyMVar_
takes care of that for us. This is similar to using defer
in the original example.
The doIncrement
function increments a named counter in a loop, similar to the original example.
In the main
function, we create three threads using forkIO
, each calling doIncrement
with different parameters. We then wait for all threads to finish using takeMVar
.
Running the program shows that the counters are updated as expected:
$ runhaskell mutexes.hs
fromList [("a",20000),("b",10000)]
Next, we’ll look at implementing this same state management task using only threads and channels.