# Concurrency
# Spawning Threads with forkIO
Haskell supports many forms of concurrency and the most obvious being forking a thread using forkIO
.
The function forkIO :: IO () -> IO ThreadId
takes an IO
action and returns its ThreadId
, meanwhile the action will be run in the background.
We can demonstrate this quite succinctly using ghci
:
Prelude Control.Concurrent> forkIO $ (print . sum) [1..100000000]
ThreadId 290
Prelude Control.Concurrent> forkIO $ print "hi!"
"hi!"
-- some time later....
Prelude Control.Concurrent> 50000005000000
Both actions will run in the background, and the second is almost guaranteed to finish before the last!
# Communicating between Threads with MVar
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 newMVar a
newMVar :: a -> IO (MVar a)
-- creates a newMVar
with the given valuetakeMVar :: MVar a -> IO a
-- retrieves the value from the givenMVar
, or blocks until one is availableputMVar :: MVar a -> a -> IO ()
-- puts the given value in theMVar
, or blocks until it's empty
Let'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 (opens new window). 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:
- Thread 1 reads
ma
and blocksma
- Thread 2 reads
mb
and thus blocksmb
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!
# Atomic Blocks with Software Transactional Memory
Another powerful & mature concurrency tool in Haskell is Software Transactional Memory, which allows for multiple threads to write to a single variable of type TVar a
in an atomic manner.
TVar a
is the main type associated with the STM
(opens new window) monad and stands for transactional variable. They're used much like MVar
but within the STM
monad through the following functions:
# atomically :: STM a -> IO a
Perform a series of STM actions atomically.
# readTVar :: TVar a -> STM a
Read the TVar
's value, e.g.:
# writeTVar :: TVar a -> a -> STM ()
Write a value to the given TVar
.
This example is taken from the Haskell Wiki:
# Remarks
Good resources for learning about concurrent and parallel programming in Haskell are: