Concurrency
Spawning Threads with forkIO
Section titled “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 290Prelude Control.Concurrent> forkIO $ print "hi!""hi!"-- some time later....Prelude Control.Concurrent> 50000005000000Both actions will run in the background, and the second is almost guaranteed to finish before the last!
Communicating between Threads with MVar
Section titled “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 anewMVar :: a -> IO (MVar a)— creates a newMVarwith 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.Concurrentmain = 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 loopAs 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 bWe run the the two functions with some MVars
concurrent ma mb -- new thread 1concurrent mb ma -- new thread 2What could happen is that:
- Thread 1 reads
maand blocksma - Thread 2 reads
mband 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
Section titled “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 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
Section titled “atomically :: STM a -> IO a”Perform a series of STM actions atomically.
readTVar :: TVar a -> STM a
Section titled “readTVar :: TVar a -> STM a”Read the TVar’s value, e.g.:
writeTVar :: TVar a -> a -> STM ()
Section titled “writeTVar :: TVar a -> a -> STM ()”Write a value to the given TVar.
This example is taken from the Haskell Wiki:
Remarks
Section titled “Remarks”Good resources for learning about concurrent and parallel programming in Haskell are: