Software Transactional Memory

Run an STM transaction in IO Block until a read TVar changes

Try an alternative if the first transaction retries TMVar, TChan, TQueue, TBQueue

Transactional MVar, channels, queues (stm package) check b = if b then pure () else retry

STM transactions are composable (unlike locks), deadlock-free, and automatically retried on conflict. You can't do IO inside STM; the type system prevents it.

import Control.Concurrent.STM
import Control.Concurrent (forkIO)
import Control.Monad

type Account = TVar Int

transfer :: Account -> Account -> Int -> STM ()
transfer from to amount = do
  balance <- readTVar from
  when (balance < amount) retry        -- block until the balance changes, then retry
  modifyTVar' from (subtract amount)
  modifyTVar' to (+ amount)

-- Composable alternatives
transferOrFail :: Account -> Account -> Int -> STM Bool
transferOrFail from to amount =
  (transfer from to amount >> pure True) `orElse` pure False

main :: IO ()
main = do
  alice <- newTVarIO 100
  bob <- newTVarIO 0
  atomically (transfer alice bob 30)                   -- runs atomically: all or nothing
  ok <- atomically (transferOrFail alice bob 500)
  print ok                                             -- False
  balances <- atomically ((,) <$> readTVar alice <*> readTVar bob)
  print balances                                       -- (70,30)

  -- A blocking transfer that completes when funds arrive
  _ <- forkIO $ atomically (transfer bob alice 50)     -- waits: bob has only 30
  atomically (modifyTVar' bob (+ 100))                 -- now the waiting transfer can proceed