帮助理解Haskell中的MVar示例

Anu*_*ain 8 concurrency haskell

我正在尝试理解GHC最新文档中的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)
Run Code Online (Sandbox Code Playgroud)

我理解大部分计划,但有两个问题 -

  1. putSkipChan原子一样的操作吗?它似乎putMVar通过先做一个避免阻塞takeMVar.但是,那不是失败,如果别的要求putMVar后,takeMVar但之前putMVar?在这种情况下,程序似乎会永远阻止.
  2. 为什么dupSkipChan附加sem到信号量列表SkipChan?不是那样做的getSkipChan.在我看来,在尝试两次唤醒同一个信号量时,调用dupSkipChan后跟getSkipChan(这似乎是你需要做多个读者)会导致阻塞putSkipChan

Chr*_*icz 5

  1. 你是对的,另一个线程可以调用putMVar main并搞砸了putSkipChan.但是创建上述代码的模块不会导出SkipChan构造函数,因此这样的恶意操作是不可能的.

  2. dupSkipChan创建一个新的 emptyMVar调用sem并将其添加到main中的列表中.它不会添加在其中创建的预先存在的一个newSkipChan.因此没有障碍.

向其他读者解释这个问题和评论的更多内容:这个想法是可能有多个读者线程.最初SkipChan main sem1是唯一这样的读者. dupSkipChan做一个SkipChan main sem2.如果有成千上万的读者,那么你不会想要通知他们所有的新值putSkipChan,因此设计是getSkipChan将其sem放入main中的列表中.初始化SkipChan完成,newSkipChan并且dupSkipChan还包括将新空值sem放入main中的列表中.

上述初始化和设计意味着第一次getSkipChan获得已经写入的最近的过去值(或者阻止第一个值到达).getSkipChan对此的未来SkipChan将始终获得比之前获得的更新的值,并且如果该值已经可用,这些将不会阻止.