multithreadinghaskellunsafe-perform-io

unsafePerformIO in threaded applications does not work


Below is the source of a sample program:

When I run it from ghci both printJob and printJob2 run fine and write ten lines into a text file.

But when compiled with -threaded flag, the program writes only one line.

I have ghc 7.0.3 on ArchLinux

Here's the compile command:

ghc -threaded -Wall -O2 -rtsopts -with-rtsopts=-N -o testmvar testmvar.hs

What am I am doing wrong ? Why it does not work in threaded mode ?

import Control.Concurrent.MVar
import Control.Concurrent (forkIO)
import Control.Exception (bracket)
import Control.Monad (forM_)
import System.IO.Unsafe (unsafePerformIO)
import System.IO (hPutStrLn, stderr)


{-# NOINLINE p #-}
p :: MVar Int
p = unsafePerformIO $ newMVar (1::Int)


{-# NOINLINE printJob #-}
printJob x = bracket (takeMVar p) (putMVar p . (+ 1))
                   (\a -> do
                       appendFile "mvarlog.txt" $ "Input: " ++ x ++ "; Counter: " ++ show a ++ "\n"
                       )


{-# NOINLINE printJob2 #-}
printJob2 = unsafePerformIO $ do
   p2 <- newEmptyMVar
   return $ (\x -> bracket (putMVar p2 True) (\_ -> takeMVar p2)
                   (\_ -> do
                       appendFile "mvarlog.txt" $ "preformed " ++ x ++ "\n"
                   ))

main = do
  forM_ [1..10]
    (\x -> forkIO $ printJob (show x))

EDIT: hammar pointed out that if main application exits earlier than all spawned threads, then they will be killed and suggested to add a delay at the end of main. I did and as he predicted, it works.


Solution

  • The problem is that your main thread finishes too soon, and when the main thread of a Haskell program finishes, all other threads get killed automatically. Depending on how the threads get scheduled, this might happen before any of the threads have had a chance to run at all.

    A quick and dirty solution is to simply add a threadDelay at the end of main, though a more robust method would be to use a synchronization primitive like an MVar to signal when it's OK for the main thread to finish.

    For example:

    main = do
      vars <- forM [1..10] $ \x -> do
        done <- newEmptyMVar -- Each thread gets an MVar to signal when it's done
        forkIO $ printJob (show x) >> putMVar done ()
        return done
    
      -- Wait for all threads to finish before exiting
      mapM_ takeMVar vars