34 votes

Suppression d'un fil d'exécution lorsque MVar fait l'objet d'une collecte de déchets.

J'ai un fil d'exécution qui lit les données de façon répétée à partir d'un MVar et effectue un travail utile sur ces données. Après un certain temps, le reste du programme oublie ce fil d'exécution, ce qui signifie qu'il attendra un MVar vide et se sentira très seul. Ma question est la suivante :

Le MVar sera-t-il récupéré si les threads n'y écrivent plus, par exemple parce qu'ils l'attendent tous ? La collecte des déchets tuera-t-elle les threads en attente ? Si ce n'est pas le cas, puis-je indiquer d'une manière ou d'une autre au compilateur que le MVar doit être récupéré et que le thread doit être tué ?

EDIT : Je devrais probablement préciser l'objet de ma question. Je ne souhaite pas une protection générale contre les blocages ; au lieu de cela, ce que je voudrais faire est de lier la vie du fil de travail à la vie d'une valeur (comme dans : les valeurs mortes sont réclamées par la collecte des ordures). En d'autres termes, le fil de travail est une ressource que je voudrais libérer non pas manuellement, mais lorsqu'une certaine valeur (le MVar ou un dérivé) est récupérée par le ramassage des ordures.

Voici un exemple de programme qui démontre ce que j'ai en tête

import Control.Concurrent
import Control.Concurrent.MVar

main = do
    something
    -- the thread forked in  something  can  be killed here
    -- because the  MVar  used for communication is no longer in scope
    etc

something = do
    v <- newEmptyMVar
    forkIO $ forever $ work =<< takeMVar v
    putMVar v "Haskell"
    putMVar v "42"

En d'autres termes, je veux que le thread soit tué lorsque je ne peux plus communiquer avec lui, c'est-à-dire lorsque la MVar utilisée pour la communication n'est plus dans la portée. Comment faire ?

27voto

Simon Marlow Points 9153

Cela fonctionnera simplement : lorsque le MVar n'est atteignable que par le thread qui est bloqué dessus, alors le thread est envoyé à l'adresse suivante BlockedIndefinitelyOnMVar ce qui entraîne normalement sa mort silencieuse (le gestionnaire d'exception par défaut d'un thread ignore cette exception).

BTW, pour faire un peu de nettoyage quand le fil meurt, vous voudrez utiliser forkFinally (que je juste ajouté à Control.Concurrent ).

22voto

Don Stewart Points 94361

Si vous êtes chanceux, vous obtiendrez un "BloquéIndefinimentOnMVar" indiquant que vous attendez un MVar sur lequel aucun thread n'écrira jamais.

Mais, pour citer Ed Yang,

GHC sait seulement qu'un thread peut être considéré comme une poubelle s'il n'y a pas de références à ce thread. Qui détient une référence au thread ? Le MVar, car le thread est bloqué sur cette structure de données et s'est s'est ajouté à la liste des bloqueurs de cette structure. Qui garde le MVar en vie ? Pourquoi, notre fermeture qui contient un appel à takeMVar. Donc le thread reste.

Sans un peu de travail (qui serait, soit dit en passant, assez intéressant à voir), BlockedIndefinitelyOnMVar n'est pas un mécanisme manifestement utile pour donner à vos programmes Haskell une protection contre les blocages.

GHC ne peut tout simplement pas résoudre le problème en général de savoir si votre fil de discussion va progresser.

Une meilleure approche serait de terminer explicitement les threads en leur envoyant un message Done message. Par exemple, il suffit de transformer le type de message en une valeur facultative qui comprend également une valeur de fin de message :

import Control.Concurrent
import Control.Concurrent.MVar
import Control.Monad
import Control.Exception
import Prelude hiding (catch)

main = do
    something

    threadDelay (10 * 10^6)
    print "Still here"

something = do
    v <- newEmptyMVar
    forkIO $
        finally
            (let go = do x <- takeMVar v
                         case x of
                            Nothing -> return ()
                            Just v  -> print v >> go
             in go)
            (print "Done!")

    putMVar v $ Just "Haskell"
    putMVar v $ Just "42"

    putMVar v Nothing

et nous obtenons le nettoyage correct :

$ ./A
"Haskell"
"42"
"Done!"
"Still here"

11voto

Chris Kuklewicz Points 6789

J'ai testé le simple MVar faible et il a été finalisé et tué. Le code est le suivant :

import Control.Monad
import Control.Exception
import Control.Concurrent
import Control.Concurrent.MVar
import System.Mem(performGC)
import System.Mem.Weak

dologger :: MVar String -> IO ()
dologger mv = do
  tid <- myThreadId
  weak <- mkWeakPtr mv (Just (putStrLn "X" >> killThread tid))
  logger weak

logger :: Weak (MVar String) -> IO ()
logger weak = act where
  act = do
    v <- deRefWeak weak
    case v of
      Just mv -> do
       a <- try (takeMVar mv) :: IO (Either SomeException String)
       print a
       either (\_ -> return ()) (\_ -> act) a
      Nothing -> return ()

play mv = act where
  act = do
    c <- getLine
    if c=="quit" then return ()
       else putMVar mv c >> act

doplay mv = do
  forkIO (dologger mv)
  play mv

main = do
  putStrLn "Enter a string to escape, or quit to exit"
  mv <- newEmptyMVar
  doplay mv

  putStrLn "*"
  performGC
  putStrLn "*"
  yield
  putStrLn "*"
  threadDelay (10^6)
  putStrLn "*"

La session avec le programme était :

(chrisk)-(/tmp)
(! 624)-> ghc -threaded -rtsopts --make weak2.hs 
[1 of 1] Compiling Main             ( weak2.hs, weak2.o )
Linking weak2 ...

(chrisk)-(/tmp)
(! 625)-> ./weak2 +RTS -N4 -RTS
Enter a string to escape, or quit to exit
This is a test
Right "This is a test"
Tab Tab
Right "Tab\tTab"
quit
*
*
X
*
Left thread killed
*

Donc le blocage sur takeMVar a fait pas maintenir le MVar en vie sur ghc-7.4.1 malgré les attentes.

1voto

dmbarbour Points 176

Alors que BlockedIndefinitelyOnMVar devrait fonctionner, envisagez également d'utiliser Finaliseurs ForeignPointer . Le rôle normal de ces derniers est de supprimer les structures C qui ne sont plus accessibles en Haskell. Cependant, vous pouvez leur attacher n'importe quel finalisateur IO.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X