UnsafePerformi nelle applicazioni thread non funziona
-
12-11-2019 - |
Domanda
Di seguito è riportata la fonte di un programma di esempio:
Quando lo eseguo da GHCI entrambi stampa e printjob2 eseguono bene e scrivo dieci righe in un file di testo.
Ma quando compilato con bandiera thread, il programma scrive solo una riga.
Ho GHC 7.0.3 su Archlinux
Ecco il comando di compilazione:
ghc -threaded -Wall -O2 -rtsopts -with-rtsopts=-N -o testmvar testmvar.hs
Cosa sto facendo di sbagliato? Perché non funziona in modalità thread?
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 ha sottolineato che se l'applicazione principale esce prima di tutti i thread generati, verranno uccisi e suggeriti di aggiungere un ritardo alla fine di Main. L'ho fatto e come ha previsto, funziona.
Soluzione
Il problema è che il thread principale termina troppo presto e quando il filo principale di un programma Haskell termina, tutti gli altri thread vengono uccisi automaticamente. A seconda di come vengono programmati i thread, ciò potrebbe accadere prima che uno qualsiasi dei thread abbia avuto la possibilità di funzionare.
Una soluzione rapida e sporca è semplicemente aggiungere un threadDelay
alla fine di main
, sebbene un metodo più robusto sarebbe quello di utilizzare una primitiva di sincronizzazione come un MVar
Segnalare quando va bene che il thread principale finisca.
Per esempio:
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
Altri suggerimenti
Ovviamente non funziona. L'uso di UnsafePerforio torna sempre a perseguitarti. Struttura il tuo codice per non usarlo. Usarlo per creare variabili globali non è un uso legittimo. Ecco a cosa serve il lettore monade. Non hai bisogno di unfeperformi per nulla a Haskell.
Mi uccide quando le persone raccomandano questo "trucco" quando è chiaramente rotto. È come il cieco che guida i ciechi. Basta non farlo e non avrai problemi a usare Haskell. Haskell ha soluzioni davvero belle ed eleganti ad ogni problema che lo fai, ma se insisti a combatterlo invece di impararlo, continuerai sempre a correre in bug.