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 anewMVar :: 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 MVars
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
What could happen is that:
ma and blocks mamb and thus blocks mbNow 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!