Reading — step 1 of 7
Learn
Haskell's concurrency story is among the cleanest of any language — a combination of cheap green threads (forkIO), shared variables (MVar), and Software Transactional Memory (STM). Real World Haskell's concurrency chapter and the Parallel and Concurrent Programming in Haskell book are the references.
forkIO — lightweight threads
import Control.Concurrent
main :: IO ()
main = do
forkIO $ do
threadDelay 1000000 -- 1 second
putStrLn "hello from thread"
putStrLn "main continues"
threadDelay 2000000 -- wait so the thread completes
forkIO is super cheap — millions of threads possible. The Haskell runtime maps them onto OS threads efficiently.
MVar — shared mutable variable with locking
import Control.Concurrent.MVar
main :: IO ()
main = do
counter <- newMVar 0
forkIO $ replicateM_ 1000 (modifyMVar_ counter (return . (+1)))
forkIO $ replicateM_ 1000 (modifyMVar_ counter (return . (+1)))
threadDelay 100000
final <- readMVar counter
print final -- 2000 (eventually)
newMVar— create with initial valuetakeMVar/putMVar— atomic take and put (blocks if empty/full)modifyMVar_— atomic update; safe for compound operationsreadMVar— read without removing
Blocking semantics:
takeMVaron empty MVar blocks until something arrivesputMVaron full MVar blocks until something is taken
MVar is essentially a one-cell channel — useful for synchronization too.
STM — Software Transactional Memory
The killer feature: composable atomic operations.
import Control.Concurrent.STM
main :: IO ()
main = do
balance1 <- newTVarIO 100
balance2 <- newTVarIO 50
-- Atomic transfer:
atomically $ do
b1 <- readTVar balance1
when (b1 < 30) retry -- block until balance1 has 30+
writeTVar balance1 (b1 - 30)
b2 <- readTVar balance2
writeTVar balance2 (b2 + 30)
The atomically block runs as a transaction:
- If conflicting changes happen elsewhere, the transaction aborts and retries
retryblocks until SOME relevant variable changes (e.g., until balance1 has enough money)- Whole compound operation is atomic — no halfway states visible
Why STM is great:
- Compositional — combine smaller transactions into bigger ones with
do - No deadlocks — no explicit locks
- Optimistic — no blocking unless retry is hit
- Type system enforces purity (no IO inside
atomically)
async — futures with cancellation
The async library provides a Promise-like interface:
import Control.Concurrent.Async
main :: IO ()
main = do
a <- async (heavyComputation 1)
b <- async (heavyComputation 2)
resultA <- wait a
resultB <- wait b
print (resultA + resultB)
async :: IO a -> IO (Async a)— start an action concurrentlywait :: Async a -> IO a— block for resultcancel :: Async a -> IO ()— kill the asyncconcurrently :: IO a -> IO b -> IO (a, b)— run two in parallelrace :: IO a -> IO b -> IO (Either a b)— first one wins, other is cancelled
Recommended over raw forkIO + MVar for most concurrent code.
Picking your concurrency tool
| Need | Tool |
|---|---|
| Lightweight async tasks | async library |
| Compositional atomic state changes | STM |
| Single shared variable with simple update | MVar |
| Many small communicating threads | TQueue (STM-based queue) |
| CPU parallelism (pure data) | parallel package's par |
Comparison with other languages
- vs Go's goroutines — similar lightweight thread model; STM cleaner than Go's channels for compound transactions
- vs Java's threads — Haskell threads cheaper, no memory model surprises (immutability default)
- vs Erlang processes — Erlang processes are isolated (no shared state); Haskell shares via MVar/STM. Different trade-offs.
Common mistakes
- MVar for compound updates — atomic for individual ops, but multi-step logic can race. Use modifyMVar for read-modify-write.
- STM that does IO — illegal; the transaction may retry, so side effects are forbidden inside
atomically. - Forgetting to wait — async with no wait/cancel can leak. Use
withAsyncfor scope-bounded async. - forkIO without resource cleanup — children don't auto-die when parent does. async + withAsync solve this.
- Heavy lock contention with MVar — for many threads contending one variable, STM scales better.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…