A short note about using MVars in Haskell. Source is here: https://github.com/carlohamalainen/playground/tree/master/haskell/mvar.
Unlike earlier blog posts, this one should be built using Stack. Something like:
git clone https://github.com/carlohamalainen/playground.git cd playground/haskell/mvar stack build
stack ghci instead of
cabal repl. The main executable is
Here is the situation: we have a function that makes a call to some
restricted resource, say a network API, and we would like calls to this
API from our application to be serialized across multiple threads.
For the purposes of this blog post, here is a dummy function that
sleeps a bit and returns
x + 1. Pretend that it’s calling
a magical API on the network somewhere.
We have a general task that makes use of the expensive resource:
At the top level we need to run a few
doThings in parallel:
The problem with
main0 is that the calls to
getExpensiveThing can happen simultaneously, so we need to use some kind of thread synchronisation primitive. I initially thought that I’d have to use a semaphore, a queue, or something fancy, but an MVar can do the trick.
We only need three operations on
newEmptyMVar to create a new
MVar which is initially empty:
takeMVar to get the contents of the
MVar. If the
MVar is empty,
takeMVar will wait until it is full.
putMVar to put a value into an
MVar. If the
MVar is full, then
putMVar will wait
MVar is empty. If multiple threads are blocked on an
MVar, they are woken up in FIFO order.
So what we can do is have
takeMVar to block until a worker requires a value. The return value can be passed back via another
MVar, which the worker is itself waiting on. The data type
MVar is polymorphic in its type parameter, so there is no trouble
in having an
MVar of an
MVar, or an
MVar of a tuple containing another
MVar, and so on. This is what we’ll use to represent a blocking action with input value of type
a and output value of type
MVar wraps a tuple, where the first component is the raw input value of type
a, and the
second component is the
MVar in which the result value will be passed back. Here is the new
MVar is contained inside the
MVar. This way,
has a unique channel back to the calling function. I used
ScopedTypeVariables to be able to write the types of
output inline, but this is just for clarity in this blog post. Also note that
getExpensiveThing' runs forever using forever from Control.Monad.
Here is the updated
doThings that uses the
MVar to communicate with
main needs a top-level
MVar which is the first parameter to
and a forked thread to run
Now each evaluation of
threadDelay (the sensitive bit of code that represents a call to a resource) happens sequentially`` although the order is nondeterministic.
$ stack build && .stack-work/dist/x86_64-linux/Cabal-184.108.40.206/build/mvar-exe/mvar-exe doThings': thread 1 got 2 doThings': thread 5 got 6 doThings': thread 2 got 3 doThings': thread 4 got 5 doThings': thread 3 got 4
Just for fun, let’s make some helper functions to make calling a special worker via an
MVar a bit cleaner. In general,
calling a worker requires creating a results
MVar, pushing the input and results
MVar to the
and finally taking the result.
To save ourselves having to fork a worker, we can write a
combinator that takes a worker and an action and runs the action with
access to the newly created
doThings'' is a bit shorter, at the expense of not knowing (at a glance) what the
io thing is going to do.
main' is largely unchanged except for
withWorker at the top level.
*Main> main' doThings'': thread 2 got 3 doThings'': thread 3 got 4 doThings'': thread 4 got 5 doThings'': thread 1 got 2 doThings'': thread 5 got 6