我想了解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
(这似乎是拥有多个阅读器所要做的事情)会导致阻塞? 最佳答案
putMVar main
并弄乱putSkipChan
。但是创建上述代码的模块不会导出SkipChan
构造函数,因此这种流氓操作将是不可能的。 dupSkipChan
生成一个新的emptyMVar
,称为sem
,并将其添加到main的列表中。它不会添加在newSkipChan
中创建的现有目录。因此没有障碍。 为了进一步向其他读者解释这个问题和评论:想法是可能有多个读者线程。最初
SkipChan main sem1
是唯一的此类阅读器。 dupSkipChan
生成SkipChan main sem2
。如果有成千上万的读者,那么您就不想在putSkipChan
中将所有新值通知所有读者,因此设计是getSkipChan
将其sem放入main的列表中。像SkipChan
和newSkipChan
中一样完成对dupSkipChan
的初始化,还包括将新的空sem
放入main的列表中。上面的初始化和设计意味着第一个
getSkipChan
获得要写入的最新过去值(或阻塞第一个值到达)。该getSkipChan
上的将来SkipChan
将始终获得比以前获得的值更高的值,并且如果该值已可用,则不会阻塞这些值。