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.