2014-08-31 50 views
5

我在做一些实验并发和内存的知名度和跑进这种奇怪的行为(见注释在线):叉状IOREF阅读器的功能似乎停滞主线程

module Main 
    where 

import Data.IORef 
import Control.Concurrent 
import System.CPUTime 

import System.IO 

main = do 
    hSetBuffering stdout NoBuffering 

    r <- newIORef False 
    putStrLn "forking..." -- PRINTED 
    forkIO $ f r 
    threadDelay 1000000 

    putStrLn "writeIORef" -- NEVER PRINTED 
    writeIORef r True 

    threadDelay maxBound 

f :: IORef Bool -> IO() 
f r = readIORef r >>= \b-> if b then print "NEVER PRINTED" else f r 

我期待也许writeIORef不被对于子线程是可见的,但对于主线程来说并不简单(显然)失速。

编上GHC 7.8.3

cabal exec ghc -- --make -fforce-recomp -O2 -threaded visibility.hs 

./visibility +RTS -N 

跑这里发生了什么?

编辑:所以我的机器有两个真正的核心和两个超线程核心,所以+RTS -N GHC看到4个能力。每加布里埃尔·冈萨雷斯的回答我尝试了以下,看看也许调度是把两个线程都在同一个物理处理器上:

module Main 
    where 

import Data.IORef 
import Control.Concurrent  
import GHC.Conc(threadCapability,myThreadId,forkOn) 

main = do  
    r <- newIORef False 
    putStrLn "going..." 

    (cap,_) <- threadCapability =<< myThreadId 
    forkOn (cap+1) $ f r     -- TRIED cap+1, +2, +3.... 
    threadDelay 1000000 

    putStrLn "writeIORef"     -- BUT THIS STILL NEVER RUNS 
    writeIORef r True 

    threadDelay maxBound 

f :: IORef Bool -> IO() 
f r = readIORef r >>= \b-> if b then print "A" else f r 

回答

3

ghc只挂起线程在明确安全点,当内存分配这只是。我相信你分叉的线程永远不会分配内存,所以它永远不会放弃对其他线程的控制。因此,一旦编译器安排分叉线程(有时在你的threadDelay的中间),你的主线程永远不会前进。

您可以在“轻量级线程和平行度”部分了解有关安全点here的更多信息。

编辑:正如托马斯所说,你可以使用Control.Concurrent.yield明确放弃控制,当你遇到这样的情况。

+2

正是。一个解决方案是使用“yield”来分割没有分配或其他类似点的块。因此,对于这个问题中的忙碌等待,我们会有'... else yield >> f r'。显然,繁忙的循环首先是一个糟糕的主意。另一种方法是使用'MVar'和'takeMVar'来代替信号。 – 2014-09-01 03:07:22

+0

对不起,也许我正在密集...为什么我需要分叉线程来控制回主线程,以便让'putStrLn“writeIORef”'运行?我用'+ RTS -N'运行,用'-threaded'编译;两个线程不应该同时运行吗? – jberryman 2014-09-01 12:45:16

+0

请参阅我的编辑,以及 – jberryman 2014-09-01 14:47:33