In order to get familiar with STM in Haskell, I wrote the following solution to the Dining Philosophers problem:
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import System.Random
type Fork = TVar Bool
type StringBuffer = TChan String
philosopherNames :: [String]
philosopherNames = map show ([1..] :: [Int])
logThinking :: String -> StringBuffer -> STM ()
logThinking name buffer = writeTChan buffer $ name ++ " is thinking..."
logEating :: String -> StringBuffer -> STM ()
logEating name buffer = writeTChan buffer $ name ++ " is eating..."
firstLogEntry :: StringBuffer -> STM String
firstLogEntry buffer = do empty <- isEmptyTChan buffer
                          if empty then retry
                                   else readTChan buffer
takeForks :: Fork -> Fork -> STM ()
takeForks left right = do leftUsed <- readTVar left
                          rightUsed <- readTVar right
                          if leftUsed || rightUsed
                             then retry
                             else do writeTVar left True
                                     writeTVar right True
putForks :: Fork -> Fork -> STM ()
putForks left right = do writeTVar left False
                         writeTVar right False
philosopher :: String -> StringBuffer -> Fork -> Fork -> IO ()
philosopher name out left right = do atomically $ logThinking name out
                                     randomDelay
                                     atomically $ takeForks left right
                                     atomically $ logEating name out
                                     randomDelay
                                     atomically $ putForks left right
randomDelay :: IO ()
randomDelay = do delay <- getStdRandom(randomR (1,3))
                 threadDelay (delay * 1000000)
main :: IO ()
main = do let n = 8
          forks <- replicateM n $ newTVarIO False
          buffer <- newTChanIO
          forM_ [0 .. n - 1] $ \i ->
              do let left = forks !! i
                     right = forks !! ((i + 1) `mod` n)
                     name = philosopherNames !! i
                 forkIO $ forever $ philosopher name buffer left right
          forever $ do str <- atomically $ firstLogEntry buffer
                       putStrLn str
When I compile and run my solution, it seems that no obvious concurrency issues exist: Each philosopher will eventually eat and no philosopher seems to be favoured. However, if I remove the randomDelay statements from philosopher, compile and run, the output of my program looks like the following:
1 is thinking...
1 is eating...
1 is thinking...
1 is eating...
2 is thinking...
2 is eating...
2 is thinking...
2 is eating...
2 is thinking...
2 is eating...
2 is thinking...
About 2500 lines later...
2 is thinking...
2 is eating...
2 is thinking...
3 is thinking...
3 is eating...
3 is thinking...
3 is eating...
And so on...
What is happening in this case?
You need to compile it with the threaded runtime and enabled rtsopts, and run it with +RTS -N (or +RTS -Nk where k is the number of threads. With that, I get output like
8 is eating...
6 is eating...
4 is thinking...
6 is thinking...
4 is eating...
7 is eating...
8 is thinking...
4 is thinking...
7 is thinking...
8 is eating...
4 is eating...
4 is thinking...
4 is eating...
6 is eating...
4 is thinking...
The point is that for another philosopher to think/eat, a context switch must happen if you don't have several hardware threads at your disposition. Such a context switch doesn't happen very often here, where not much allocation is done, so each philosopher has a lot of time to think and eat a lot before the next one's turn comes up.
With enough threads at your disposition, all philosophers can concurrently try to reach for the forks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With