unsafePerformIO в многопоточных приложениях не работает
Ниже приведен источник примера программы:
Когда я запускаю его из ghci, и printJob, и printJob2 работают нормально и записывают десять строк в текстовый файл.
Но при компиляции с флагом -threaded программа записывает только одну строку.
У меня ghc 7.0.3 на ArchLinux
Вот команда компиляции:
ghc -threaded -Wall -O2 -rtsopts -with-rtsopts=-N -o testmvar testmvar.hs
Что я делаю не так? Почему не работает в многопоточном режиме?
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))
РЕДАКТИРОВАТЬ: Хаммар указал, что если основное приложение выходит раньше, чем все порожденные потоки, то они будут уничтожены, и предложил добавить задержку в конце основного. Я сделал, и, как он предсказал, это работает.
2 ответа
Проблема в том, что ваш основной поток заканчивается слишком рано, и когда основной поток программы на Haskell заканчивается, все остальные потоки автоматически уничтожаются. В зависимости от того, как запланированы потоки, это может произойти до того, как какой-либо из потоков сможет вообще работать.
Быстрое и грязное решение - просто добавить threadDelay
в конце main
хотя более надежным методом было бы использовать примитив синхронизации, такой как MVar
сигнализировать, когда все в порядке для завершения основного потока.
Например:
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
Конечно, это не работает. Использование unsafePerformIO всегда дает о себе знать. Структурируйте свой код так, чтобы он не использовался. Использование его для создания глобальных переменных не является законным использованием. Вот для чего читательская монада. Вам не нужно unsafePerformIO для чего-либо в Haskell.
Меня убивает, когда люди рекомендуют этот "трюк", когда он явно сломан. Это как слепой ведущий слепого. Только не делайте этого, и у вас не будет проблем с использованием Haskell. У Haskell есть действительно красивые и элегантные решения для каждой проблемы, которую вы бросаете, но если вы будете настаивать на том, чтобы бороться с этим, а не учиться, то вы всегда будете сталкиваться с ошибками.