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 -
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.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?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.
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.
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