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.

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top