Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Help understanding MVar example in Haskell

I'm trying to understand the MVar example in the GHC latest docs -

data SkipChan a = SkipChan (MVar (a, [MVar ()])) (MVar ())

newSkipChan :: IO (SkipChan a)
newSkipChan = do
     sem <- newEmptyMVar
     main <- newMVar (undefined, [sem])
     return (SkipChan main sem)

putSkipChan :: SkipChan a -> a -> IO ()
putSkipChan (SkipChan main _) v = do
     (_, sems) <- takeMVar main
     putMVar main (v, [])
     mapM_ (sem -> putMVar sem ()) sems

getSkipChan :: SkipChan a -> IO a
getSkipChan (SkipChan main sem) = do
     takeMVar sem
     (v, sems) <- takeMVar main
     putMVar main (v, sem:sems)
     return v

dupSkipChan :: SkipChan a -> IO (SkipChan a)
dupSkipChan (SkipChan main _) = do
     sem <- newEmptyMVar
     (v, sems) <- takeMVar main
     putMVar main (v, sem:sems)
     return (SkipChan main sem)

I understand most of the program but for two questions -

  1. Are operations like putSkipChan atomic? It seems to avoid blocking on putMVar by first doing a takeMVar. But wouldn't that fail if something else calls putMVar after the takeMVar but before the putMVar? In such cases, it seems the program would block forever.
  2. Why does dupSkipChan append sem to the list of semaphores in the SkipChan? Isn't that done by getSkipChan. It seems to me that calling dupSkipChan followed by getSkipChan (which seems to be what you have to do to have multiple readers) would cause a block when putSkipChan tries to wake up the same semaphore twice?
like image 581
Anupam Jain Avatar asked Aug 31 '11 12:08

Anupam Jain


1 Answers

  1. You are correct, another thread could call putMVar main and mess up putSkipChan. But the module creating the above code would not export the SkipChan constructor so such a rogue operation would be impossible.

  2. dupSkipChan makes a new emptyMVar called sem and adds that to the list in main. It does not add the pre-existing one that was created in newSkipChan. Thus there is no block.

To explain more to other readers of this question and comment: The idea is that there may be multiple reader threads. Initially SkipChan main sem1 is the only such reader. dupSkipChan makes a SkipChan main sem2. If there are thousands of readers then you would not want to notify all of them of a new value in putSkipChan, thus the design is that getSkipChan puts its sem into the list in main. Initializing SkipChan as done in newSkipChan and dupSkipChan also includes putting the new empty sem into the list in main.

The above initialization and design means that the first getSkipChan obtains the most recent past value to have been written (or block for the first value to arrive). Future getSkipChan on that SkipChan will always get a newer value than any gotten before, and these will not block if that value is already available.

like image 103
Chris Kuklewicz Avatar answered Oct 11 '22 01:10

Chris Kuklewicz