我看到混合MVar和Weak的问题:即使MVar仍然可以从函数的范围访问(并且主线程阻塞它),Weak认为它应该最终确定。在启用优化的情况下编译时,似乎只会发生此行为。
平台:64位Linux
转载GHC版本:6.10.4,6.12.3,7.0.4,7.2.2,7.4.1
module Main (main) where
import Control.Concurrent
import Control.Monad (forever, forM_)
import Data.IORef
import System.Mem
import System.Mem.Weak
dispatchPendingCalls :: IORef [Weak (MVar ())] -> IO ()
dispatchPendingCalls ref = forever $ do
threadDelay 100000
pending <- atomicModifyIORef ref (\p -> ([], p))
forM_ pending (\weak -> do
maybeMVar <- deRefWeak weak
case maybeMVar of
Just mvar -> putMVar mvar ()
Nothing -> putStrLn "dispatchPendingCalls: weak mvar is Nothing")
call :: IORef [Weak (MVar ())] -> IO ()
call ref = do
mvar <- newEmptyMVar
weak <- mkWeakPtr mvar (Just (putStrLn "call: finalising weak"))
putStrLn "call: about to insert weak into list"
atomicModifyIORef ref (\p -> (weak : p, ()))
putStrLn "call: inserted weak into list"
performGC
takeMVar mvar
putStrLn "call: took from mvar"
main :: IO ()
main = do
pendingCalls <- newIORef []
_ <- forkIO (dispatchPendingCalls pendingCalls)
call pendingCalls
预期产出:
$ ghc --make WeakMvar.hs
$ ./WeakMvar
call: about to insert weak into list
call: inserted weak into list
call: took from mvar
$
实际输出:
$ ghc --make -O2 WeakMvar.hs
$ ./WeakMvar
call: about to insert weak into list
call: inserted weak into list
call: finalizing weak
dispatchPendingCalls: weak mvar is Nothing
(never exits)
为什么会这样?如果我正确地阅读System.Mem.Weak
文档,那么takeMVar mvar
行应该保持mvar活着,从而保持其弱指针有效。相反,弱指针认为MVar在对takeMVar
的调用返回之前已无法访问。
答案 0 :(得分:1)
尝试在BlockedIndefinitelyOnMVar
的{{1}}中抓取takeMVar
(由默认IIR处理,因此您无法看到)。我想使用call
会阻止GC注意您对Weak
中MVar
的引用,因此会收集垃圾?
答案 1 :(得分:1)
这几乎可以肯定是因为GHC的优化倾向于删除附加终结器的数据结构,导致终结器运行得太早。也就是说,终结器引用MVar
数据构造函数而不是底层MVar#
。 current docs对此有一些警告。如果我使用Control.Concurrent.MVar.mkWeakMVar
,我会看到预期的输出(使用ghc-7.6.3)。