Stateful Goroutines in Haskell
Our example demonstrates how to manage state using concurrent processes in Haskell. This approach aligns with Haskell’s philosophy of immutability and pure functions, while still allowing for stateful computations.
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad (forM_, replicateM_)
import System.Random (randomRIO)
data ReadOp = ReadOp Int (TMVar Int)
data WriteOp = WriteOp Int Int (TMVar Bool)
main :: IO ()
main = do
-- Create channels for read and write operations
readChan <- newTChanIO
writeChan <- newTChanIO
-- Create TVars to count operations
readOps <- newTVarIO 0
writeOps <- newTVarIO 0
-- Start the state-managing thread
forkIO $ stateManager readChan writeChan
-- Start read operation threads
replicateM_ 100 $ forkIO $ forever $ do
key <- randomRIO (0, 4)
resp <- newEmptyTMVarIO
atomically $ writeTChan readChan (ReadOp key resp)
_ <- atomically $ takeTMVar resp
atomically $ modifyTVar' readOps (+1)
threadDelay 1000
-- Start write operation threads
replicateM_ 10 $ forkIO $ forever $ do
key <- randomRIO (0, 4)
val <- randomRIO (0, 99)
resp <- newEmptyTMVarIO
atomically $ writeTChan writeChan (WriteOp key val resp)
_ <- atomically $ takeTMVar resp
atomically $ modifyTVar' writeOps (+1)
threadDelay 1000
-- Let the threads run for a second
threadDelay 1000000
-- Print the final operation counts
readOpsFinal <- atomically $ readTVar readOps
writeOpsFinal <- atomically $ readTVar writeOps
putStrLn $ "readOps: " ++ show readOpsFinal
putStrLn $ "writeOps: " ++ show writeOpsFinal
stateManager :: TChan ReadOp -> TChan WriteOp -> IO ()
stateManager readChan writeChan = do
stateVar <- newTVarIO (mempty :: Map Int Int)
forever $ atomically $ do
state <- readTVar stateVar
orElse
(do ReadOp key resp <- readTChan readChan
let val = Map.findWithDefault 0 key state
putTMVar resp val)
(do WriteOp key val resp <- readTChan writeChan
writeTVar stateVar (Map.insert key val state)
putTMVar resp True)
In this Haskell version, we use Software Transactional Memory (STM) to manage concurrent access to shared state. The stateManager
function owns the state, which is a Map
stored in a TVar
. This function repeatedly selects between read and write operations, responding to requests as they arrive.
We start 100 threads to issue reads and 10 threads to issue writes. Each operation is performed by sending a request through the appropriate channel and then waiting for a response.
The program runs for a second, then reports the total number of read and write operations performed.
To run the program:
$ ghc -threaded stateful_concurrency.hs
$ ./stateful_concurrency
readOps: 71708
writeOps: 7177
This Haskell implementation demonstrates concurrent state management using STM, which provides a safe and composable way to handle shared mutable state. It’s particularly well-suited for scenarios involving multiple concurrent operations on shared data structures.