我正在尝试理解GHC latest docs -
中的MVar示例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)
我理解大部分课程,但有两个问题 -
putSkipChan
这样的操作是原子的吗?似乎首先通过putMVar
来避免阻止takeMVar
。但是如果在putMVar
之后但在takeMVar
之前调用了putMVar
,那么会不会失败?在这种情况下,程序似乎会永远阻止。dupSkipChan
会将sem
附加到SkipChan
的信号量列表中?不是getSkipChan
完成的。在我看来,调用dupSkipChan
后跟getSkipChan
(这似乎是你 要做多少读者的事情)会在putSkipChan
时导致阻塞试图两次唤醒同一个信号量?答案 0 :(得分:5)
你是对的,另一个主题可以调用putMVar main
并弄乱putSkipChan
。但是创建上述代码的模块不会导出SkipChan
构造函数,因此无法执行此类恶意操作。
dupSkipChan
创建一个名为emptyMVar
的 new sem
,并将其添加到main中的列表中。它不会添加在newSkipChan
中创建的预先存在的一个。因此没有障碍。
向其他读者解释这个问题和评论:这个想法是可能有多个读者线程。最初SkipChan main sem1
是唯一的此类读者。 dupSkipChan
生成SkipChan main sem2
。如果有成千上万的读者,那么您不希望在putSkipChan
中通知所有读者新值,因此设计是getSkipChan
将其sem放入main中的列表中。正如SkipChan
和newSkipChan
中所做的那样初始化dupSkipChan
还包括将新的空sem
放入主列表中。
上述初始化和设计意味着第一个getSkipChan
获取已写入的最新过去值(或阻止第一个值到达)。 getSkipChan
上的未来SkipChan
将始终获得比之前获得的更新的值,并且如果该值已经可用,则不会阻止这些值。