浏览readChan的源代码,可以找到以下实现和注释,从版本4.6的基础开始:
-- |Read the next value from the 'Chan'.
readChan :: Chan a -> IO a
readChan (Chan readVar _) = do
modifyMVarMasked readVar $ read_end -> do -- Note [modifyMVarMasked]
(ChItem val new_read_end) <- readMVar read_end
-- Use readMVar here, not takeMVar,
-- else dupChan doesn't work
return (new_read_end, val)
-- Note [modifyMVarMasked]
-- This prevents a theoretical deadlock if an asynchronous exception
-- happens during the readMVar while the MVar is empty. In that case
-- the read_end MVar will be left empty, and subsequent readers will
-- deadlock. Using modifyMVarMasked prevents this. The deadlock can
-- be reproduced, but only by expanding readMVar and inserting an
-- artificial yield between its takeMVar and putMVar operations.
在基础版本4.6之前,使用的是modifyMVar,而不是modifyMVarMasked。
我不明白这里要解决什么理论问题。最后一句话指出,如果线程在包含readMVar的takeMVar和putMVar之间让步,则存在问题。但是,当readMVar在mask_下执行时,异步异常如何阻止成功获取后的put?
感谢您对理解此处问题的任何帮助。
让我们比较modifyMVar
和modifyMVarMasked
的来源,因为代码从使用一个变成了使用另一个:
modifyMVar m io =
mask $ restore -> do
a <- takeMVar m
(a',b) <- restore (io a) `onException` putMVar m a
putMVar m a'
return b
modifyMVarMasked m io =
mask_ $ do
a <- takeMVar m
(a',b) <- io a `onException` putMVar m a
putMVar m a'
return b
这里的关键是modifyMVar
在执行第二个参数之前调用restore
,而modifyMVarMasked
则不调用。因此,在旧版本的代码中,readMVar
不是在mask_
下调用的,正如您在问题中所说的那样!相反,它是在restore
下调用的,因此异步异常毕竟可以启用。
这是我的工作。
所以在readMVar中。。。
readMVar :: MVar a -> IO a
readMVar m =
mask_ $ do
a <- takeMVar m
putMVar m a
return a
尽管CCD_ 9,运行时可能在被阻塞的CCD_。请注意,在该函数中,实际上不需要处理这种情况;要么readMVar
工作了,在这种情况下,我们不会出现异步异常,要么takeMVar
永远不会成功;无论哪种方式,我们都不会让MVar
空着来破坏它。(这是正确的吗?这是我从自己的相关问题的答案中得到的。)
modifyMVar
和modifyMVarMasked
分别为:
modifyMVar :: MVar a -> (a -> IO (a,b)) -> IO b
modifyMVar m io =
mask $ restore -> do
a <- takeMVar m
(a',b) <- restore (io a) `onException` putMVar m a
putMVar m a'
return b
modifyMVarMasked :: MVar a -> (a -> IO (a,b)) -> IO b
modifyMVarMasked m io =
mask_ $ do
a <- takeMVar m
(a',b) <- io a `onException` putMVar m a
putMVar m a'
return b
其中差异在modifyMVar
中,在io a
中恢复屏蔽状态(即异步异常可能变为未屏蔽),在我们的情况下,它或多或少是readMVar
。
EDIT:虽然readMVar
也是mask_
,但现在我不明白为什么选择modifyMVarMasked
或modifyMVar
会有什么不同。。。
该注释似乎暗示yield
(插入到readMVar
中)是可中断的(我在任何地方都找不到这方面的文档),因此可能会引发异步异常,在这种情况下,readVar
将被还原(在当前和4.6之前的版本中),但在非空队列中,读卡器将看到一个空队列和块。
您可能有兴趣阅读此提交中的GHC trac,它有一个示例程序,当Control.Concurrent.Chan
和测试程序都编译为-O0
时,该程序可以始终如一地再现此错误
https://ghc.haskell.org/trac/ghc/ticket/6153
同理:
https://ghc.haskell.org/trac/ghc/ticket/5870