haskell concurrency garbage-collection

haskell - Asesinando un hilo cuando MVar es basura



concurrency garbage-collection (4)

Aunque BlockedIndefinitelyOnMVar debería funcionar, considere también el uso de finalizadores de ForeignPointer . El rol normal de estos es eliminar estructuras C que ya no son accesibles en Haskell. Sin embargo, puede adjuntar cualquier finalizador IO.

Tengo un hilo de trabajo que lee datos repetidamente de un MVar y realiza un trabajo útil sobre eso. Después de un tiempo, el resto del programa se olvida de ese hilo de trabajo, lo que significa que esperará en un MVar vacío y se volverá muy solitario. Mi pregunta es:

¿Será el MVar recogido basura si los hilos ya no le escriben, por ejemplo porque todos lo esperan? ¿La recolección de basura matará a los hilos de espera? Si ninguno de los dos, ¿puedo indicar de alguna manera al compilador que el MVar debería ser recolectado como basura y que se debe eliminar el hilo?

EDITAR: probablemente debería aclarar el propósito de mi pregunta. No deseo protección general contra interbloqueos; en cambio, lo que me gustaría hacer es vincular la vida del hilo de trabajo a la vida de un valor (como en: los valores muertos son reclamados por la recolección de basura). En otras palabras, el hilo de trabajo es un recurso que me gustaría liberar no a mano, sino cuando un cierto valor (el MVar o un derivado) es basura.


Aquí un ejemplo de programa que demuestra lo que tengo en mente

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 otras palabras, quiero que se elimine el hilo cuando ya no puedo comunicarme con él, es decir, cuando el MVar utilizado para la comunicación ya no está dentro del alcance. ¿Como hacer eso?


Probé el simple MVar débil y se finalizó y mató. El código es:

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 sesión con el programa fue:

(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 *

Así que el bloqueo en takeMVar no mantuvo vivo al MVar en ghc-7.4.1 a pesar de las expectativas.


Si tiene suerte, recibirá un "BlockedIndefinitelyOnMVar" , que indica que está esperando un MVar que ningún hilo escribirá nunca.

Pero, para citar a Ed Yang,

GHC solo sabe que un hilo puede considerarse basura si no hay referencias al hilo. ¿Quién está sosteniendo una referencia al hilo? El MVar, ya que el hilo está bloqueando en esta estructura de datos y se ha agregado a la lista de bloqueo de esto. ¿Quién mantiene vivo al MVar? Por qué, nuestro cierre que contiene un llamado para tomarMVar. Entonces el hilo se queda.

sin un poco de trabajo (que, por cierto, sería bastante interesante de ver), BlockedIndefinitelyOnMVar no es un mecanismo obviamente útil para proteger a los programas Haskell de interbloqueos.

GHC simplemente no puede resolver el problema en general de saber si su hilo progresará.

Un mejor enfoque sería terminar explícitamente los hilos al enviarles un mensaje Done . Por ejemplo, simplemente eleve su tipo de mensaje a un valor opcional que también incluya un valor de fin de mensaje:

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

y obtenemos la limpieza correcta:

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


Simplemente funcionará: cuando el MVar solo es alcanzable por el hilo que está bloqueado en él, el hilo recibe la excepción BlockedIndefinitelyOnMVar , que normalmente provocará que muera silenciosamente (el controlador de excepciones predeterminado para un hilo ignora esta excepción).

Por cierto, para hacer algo de limpieza cuando el hilo muere, querrás usar forkFinally (que acabo de agregar a Control.Concurrent ).