Concurrent Haskell
Haskell provides powerful abstractions for concurrent and parallel programming, including lightweight threads, software transactional memory (STM), and parallel evaluation strategies.
Lightweight Threads
Haskell's runtime provides extremely lightweight threads:
import Control.Concurrent
-- Simple thread example
main :: IO ()
main = do
forkIO $ putStrLn "Hello from thread!"
putStrLn "Hello from main thread"
threadDelay 1000000 -- Wait for thread to complete (1 second)
MVar for Thread Communication
MVars are mutable variables for thread communication:
import Control.Concurrent
-- MVar example
main :: IO ()
main = do
mvar <- newEmptyMVar
forkIO $ do
putStrLn "Thread is working..."
threadDelay 500000 -- Simulate work
putMVar mvar "Result"
putStrLn "Main thread waiting..."
result <- takeMVar mvar
putStrLn ("Got: " ++ result)
Software Transactional Memory (STM)
STM provides composable transactions for shared state:
import Control.Concurrent
import Control.Concurrent.STM
-- Bank account example
type Account = TVar Int
transfer :: Account -> Account -> Int -> STM ()
transfer from to amount = do
fromBalance <- readTVar from
when (fromBalance < amount) $ retry
writeTVar from (fromBalance - amount)
toBalance <- readTVar to
writeTVar to (toBalance + amount)
main :: IO ()
main = do
alice <- atomically $ newTVar 100
bob <- atomically $ newTVar 0
forkIO $ atomically $ transfer alice bob 50
threadDelay 1000000
aliceBal <- atomically $ readTVar alice
bobBal <- atomically $ readTVar bob
putStrLn $ "Alice: " ++ show aliceBal ++ ", Bob: " ++ show bobBal
Async for Managing Threads
The async package provides higher-level operations:
import Control.Concurrent.Async
-- Running computations in parallel
main :: IO ()
main = do
(result1, result2) <- concurrently
(slowComputation "First")
(slowComputation "Second")
putStrLn (result1 ++ " and " ++ result2)
slowComputation :: String -> IO String
slowComputation name = do
threadDelay 1000000
return ("Done: " ++ name)
Parallel Evaluation
The parallel package helps with parallel computation:
import Control.Parallel
import Control.Parallel.Strategies
-- Parallel map
parMap :: (a -> b) -> [a] -> [b]
parMap f xs = map f xs `using` parList rdeepseq
-- Example usage
main :: IO ()
main = do
let results = parMap (\x -> x * x) [1..100]
print (sum results)
Channels for Message Passing
Channels provide FIFO communication between threads:
import Control.Concurrent
import Control.Concurrent.Chan
-- Channel example
main :: IO ()
main = do
chan <- newChan
forkIO $ do
writeChan chan "Hello"
writeChan chan "World"
forkIO $ do
msg1 <- readChan chan
msg2 <- readChan chan
putStrLn (msg1 ++ " " ++ msg2)
threadDelay 1000000
Thread States
Haskell threads can be in different states:
- Running: Currently executing
- Blocked: Waiting for MVar, STM, or other resource
- Finished: Completed execution
Thread Scheduling
The Haskell runtime uses a sophisticated scheduler:
- Preemptive multitasking
- Work-stealing for load balancing
- Multiple capabilities (virtual CPUs)
- Fair scheduling between threads
Exceptions in Concurrent Code
Handling exceptions in concurrent code requires care:
import Control.Exception
-- Catching exceptions in threads
main :: IO ()
main = do
handle (\e -> putStrLn ("Caught: " ++ show (e :: SomeException)) $ do
thread <- async $ do
threadDelay 500000
error "Oops!"
wait thread
Best Practices
Tips for concurrent Haskell programming:
- Prefer STM over MVars for complex synchronization
- Use async for managing thread lifecycles
- Keep transactions small in STM
- Be careful with exceptions in concurrent code
- Consider thread contention and fairness