我想了解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,这不会失败吗?在这种情况下,该程序似乎将永远阻塞。
  • 为什么dupSkipChansem附加到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的列表中。像SkipChannewSkipChan中一样完成对dupSkipChan的初始化,还包括将新的空sem放入main的列表中。

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

    10-04 18:01