我正在尝试理解 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
这样的操作是原子操作吗?似乎可以通过首先执行takeMVar
来避免阻塞putMVar
。但是,如果有其他东西在takeMVar
之后但在putMVar
之前调用putMVar
,那不会失败吗?在这种情况下,程序似乎会永远阻塞。 - 为什么
dupSkipChan
将sem
附加到SkipChan
中的信号量列表中?这不是由getSkipChan
完成的吗?在我看来,调用dupSkipChan
后跟getSkipChan
(这似乎是你必须做的事情来拥有多个读者)会导致阻塞当putSkipChan
尝试两次唤醒同一个信号量时?
最佳答案
您是对的,另一个线程可能会调用
putMVar main
并搞乱putSkipChan
。但创建上述代码的模块不会导出SkipChan
构造函数,因此这种流氓操作是不可能的。dupSkipChan
创建一个名为sem
的新emptyMVar
并将其添加到 main 中的列表中。它不会添加在newSkipChan
中创建的已有的。因此没有阻塞。
向此问题和评论的其他读者解释更多内容:这个想法是可能有多个读者线程。最初,SkipChan main sem1
是唯一的此类读取器。 dupSkipChan
生成一个 SkipChan main sem2
。如果有数千名读者,那么您不希望向所有读者通知 putSkipChan
中的新值,因此设计是 getSkipChan
将其 sem 放入列表中主要的。按照 newSkipChan
和 dupSkipChan
中的方式初始化 SkipChan
还包括将新的空 sem
放入 main 的列表中。
上述初始化和设计意味着第一个getSkipChan
获取最近已写入的过去值(或第一个到达的值的 block )。该 SkipChan
上的 future getSkipChan
将始终获得比之前获得的值更新的值,并且如果该值已经可用,则这些值不会阻塞。
关于haskell - 帮助理解 Haskell 中的 MVar 示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7256860/