I was doing some experiments with concurrency and memory visibility and ran into this strange behavior (see comments inline):
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
I was expecting perhaps the writeIORef
not to be visible to the child thread, but not for the main thread to simply (apparently) stall.
Compiled on ghc 7.8.3
cabal exec ghc -- --make -fforce-recomp -O2 -threaded visibility.hs
and run with
./visibility +RTS -N
What's happening here?
EDIT: So my machine has two real cores and two hyperthreading cores, so with +RTS -N
GHC sees 4 capabilities. Per Gabriel Gonzalez's answer I tried out the following to see if maybe the scheduler was putting both threads on the same physical processor:
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
yield
to break up blocks with no allocations or other such points. So for the busy wait in the question we would have... else yield >> f r
. Obviously busy loops are generally a bad idea in the first place. One alternative is to useMVar
andtakeMVar
for signaling instead. – Amberambergris