It is very easy to pass information between threads using the MVar a
type and its accompanying functions in Control.Concurrent
:
newEmptyMVar :: IO (MVar a)
-- creates a new MVar a
newMVar :: a -> IO (MVar a)
-- creates a new MVar
with the given valuetakeMVar :: MVar a -> IO a
-- retrieves the value from the given MVar
, or blocks until one is availableputMVar :: MVar a -> a -> IO ()
-- puts the given value in the MVar
, or blocks until it's emptyLet's sum the numbers from 1 to 100 million in a thread and wait on the result:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
A more complex demonstration might be to take user input and sum in the background while waiting for more input:
main2 = loop
where
loop = do
m <- newEmptyMVar
n <- getLine
putStrLn "Calculating. Please wait"
-- In another thread, parse the user input and sum
forkIO $ putMVar m $ sum [1..(read n :: Int)]
-- In another thread, wait 'til the sum's complete then print it
forkIO $ print =<< takeMVar m
loop
As stated earlier, if you call takeMVar
and the MVar
is empty, it blocks until another thread puts something into the MVar
, which could result in a Dining Philosophers Problem. The same thing happens with putMVar
: if it's full, it'll block 'til it's empty!
Take the following function:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
We run the the two functions with some MVar
s
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
What could happen is that:
ma
and blocks ma
mb
and thus blocks mb
Now Thread 1 cannot read mb
as Thread 2 has blocked it, and Thread 2 cannot read ma
as Thread 1 has blocked it. A classic deadlock!