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
ghc only suspends threads at well-defined safe points, which are only when memory is allocated.  I believe your forked thread never allocates memory, so it never relinquishes control to other threads.  Therefore, your main thread never progresses once the compiler schedules the forked thread (sometime in the middle of your threadDelay).
You can learn more about safe points here in the section on "Lightweight Threads and Parallelism".
Edit: As Thomas mentioned, you can use Control.Concurrent.yield to explicitly relinquish control when you encounter situations like these.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With