帮助理解Haskell中的MVar示例

时间:2011-08-31 12:23:40

标签: haskell concurrency

我正在尝试理解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)

我理解大部分课程,但有两个问题 -

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

1 个答案:

答案 0 :(得分:5)

  1. 你是对的,另一个主题可以调用putMVar main并弄乱putSkipChan。但是创建上述代码的模块不会导出SkipChan构造函数,因此无法执行此类恶意操作。

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

  3. 向其他读者解释这个问题和评论:这个想法是可能有多个读者线程。最初SkipChan main sem1是唯一的此类读者。 dupSkipChan生成SkipChan main sem2。如果有成千上万的读者,那么您不希望在putSkipChan中通知所有读者新值,因此设计是getSkipChan将其sem放入main中的列表中。正如SkipChannewSkipChan中所做的那样初始化dupSkipChan还包括将新的空sem放入主列表中。

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