Concurrency in Haskell is achieved through several libraries and abstractions that make concurrent programming easier and more efficient. Some common libraries used for this purpose are ‘Control.Concurrent‘, ‘Control.Monad.Par‘, ‘Control.Parallel‘, ‘async‘, and ‘stm‘. These libraries provide various mechanisms to handle concurrency, such as threads, parallelism, and software transactional memory.
1. Control.Concurrent:
The ‘Control.Concurrent‘ library provides support for Haskell threads. A thread is an independently executing computation that can run concurrently with other threads. Haskell threads have a lightweight nature, unlike OS threads, which makes them suitable for creating large numbers of concurrent tasks.
Here’s an example of using forkIO to create two threads that run concurrently:
import Control.Concurrent
import Control.Monad
main :: IO ()
main = do
forkIO (forever printA)
forever printB
where
printA = putChar 'A' >> threadDelay 1000000
printB = putChar 'B' >> threadDelay 1500000
In this example, ‘forkIO‘ creates a new thread that runs a loop printing ’A’ every second, while the main thread runs another loop printing ’B’ every 1.5 seconds.
2. Control.Monad.Par:
The ‘Control.Monad.Par‘ library provides parallelism based on parallel evaluation strategies. It uses a monad called ‘Par‘ that allows you to express parallel computations like this:
import Control.Monad.Par
parallelSum :: Num a => [a] -> a
parallelSum xs = runPar $ do
let (left, right) = splitAt (length xs `div` 2) xs
leftVar <- spawn . return $ sum left
rightVar <- spawn . return $ sum right
leftVal <- get leftVar
rightVal <- get rightVar
return (leftVal + rightVal)
This example computes the sum of a list in parallel by recursively dividing the list into two halves, calculating their sums in parallel, and combining the results.
3. Control.Parallel:
The ‘Control.Parallel‘ library provides ‘par‘ and ‘pseq‘ combinators to introduce parallelism into the evaluation of expressions:
import Control.Parallel
parallelFib :: Int -> Int
parallelFib n
| n < 10 = fib n
| otherwise
= r `par` (s `pseq` (r+s))
where
r = parallelFib (n-1)
s = parallelFib (n-2)
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
The ‘par‘ combinator introduces parallelism, and ‘pseq‘ ensures that the computation of ‘s‘ happens before the computation of ‘(r+s)‘.
4. async:
The ‘async‘ library provides high-level, intuitive abstractions for asynchronous computations. It allows you to run IO actions concurrently and return their results.
Here’s an example of using async to concurrently fetch two web pages using the http-conduit package:
import Network.HTTP.Conduit (simpleHttp)
import Control.Concurrent.Async
main :: IO ()
main = do
(page1, page2) <- concurrently (simpleHttp "http://example.com") (simpleHttp "http://example.org")
print (length page1, length page2)
In this example, the ‘concurrently‘ function runs two IO actions concurrently and returns their results as a tuple.
5. stm:
Software Transactional Memory (STM) is a concurrency mechanism that supports composable memory transactions. It provides a way to safely manipulate shared data structures without using locks.
Here’s an example that demonstrates STM with ‘TVar‘ and the ‘atomically‘ function to safely transfer money between two bank accounts:
import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM
type Account = TVar Int
main :: IO ()
main = do
accountA <- newAccount 100
accountB <- newAccount 200
printAccount accountA
printAccount accountB
forkIO $ transfer 15 accountA accountB
transfer 10 accountB accountA
threadDelay 1000
printAccount accountA
printAccount accountB
newAccount :: Int -> IO Account
newAccount balance = atomically $ newTVar balance
printAccount :: Account -> IO ()
printAccount account = do
balance <- atomically $ readTVar account
putStrLn $ "Account balance: " ++ show balance
transfer :: Int -> Account -> Account -> IO ()
transfer value from to = atomically $ do
fromBalance <- readTVar from
when (fromBalance < value) $ retry
writeTVar from (fromBalance - value)
toBalance <- readTVar to
writeTVar to (toBalance + value)
In this example, the ‘transfer‘ function uses STM to safely perform account transfers, serializing access to the account balances without using locks or blocking.
In summary, Haskell provides a rich ecosystem of libraries and abstractions for handling concurrency. Depending on your use case, you can choose the appropriate library or mechanism to deal with concurrent tasks in Haskell programs effectively.