Quiero escribir un programa cuyo hilo principal bifurca un nuevo hilo para el cálculo y espera a que termine por un período de tiempo. Si el hilo secundario no termina en un tiempo dado, se agota el tiempo de espera y se elimina. Tengo el siguiente código para esto.
import Control.Concurrent
fibs :: Int -> Int
fibs 0 = 0
fibs 1 = 1
fibs n = fibs (n-1) + fibs (n-2)
main = do
mvar <- newEmptyMVar
tid <- forkIO $ do
threadDelay (1 * 1000 * 1000)
putMVar mvar Nothing
tid' <- forkIO $ do
if fibs 1234 == 100
then putStrLn "Incorrect answer" >> putMVar mvar (Just False)
else putStrLn "Maybe correct answer" >> putMVar mvar (Just True)
putStrLn "Waiting for result or timeout"
result <- takeMVar mvar
killThread tid
killThread tid'
Compilé el programa anterior con ghc -O2 Test.hs
y lo ghc -O2 -threaded Test.hs
ejecuté, pero en ambos casos el programa simplemente se cuelga sin imprimir nada ni salir. Si agrego un threadDelay (2 * 1000 * 1000)
al hilo de cálculo antes del if
bloque, el programa funciona como se esperaba y termina después de un segundo, ya que el hilo del temporizador puede llenar el mvar
.
¿Por qué el enhebrado no funciona como esperaba?
multithreading
haskell
concurrency
timeout
blocking
Tipo al azar
fuente
fuente
MVar
indican que es susceptible a las condiciones de carrera. Tomaría esa nota en serio.MVar
disciplina me parece bien aquí.+RTS -N
? consulte wiki.haskell.org/Concurrency para obtener más informaciónRespuestas:
GHC utiliza una especie de híbrido de multitarea cooperativa y preventiva en su implementación de concurrencia.
En el nivel de Haskell, parece preventivo porque los subprocesos no necesitan ceder explícitamente y pueden verse interrumpidos por el tiempo de ejecución en cualquier momento. Pero en el nivel de tiempo de ejecución, los subprocesos "ceden" cada vez que asignan memoria. Dado que casi todos los hilos de Haskell se asignan constantemente, esto generalmente funciona bastante bien.
Sin embargo, si un cálculo en particular puede optimizarse en un código que no se asigna, puede dejar de cooperar en el nivel de tiempo de ejecución y, por lo tanto, no tener prioridad en el nivel de Haskell. Como señaló @Carl, en realidad es la
-fomit-yields
bandera, lo que implica-O2
que permite que esto suceda:Obviamente, en el tiempo de ejecución de un solo subproceso (sin
-threaded
marca), esto significa que un subproceso puede eliminar por completo todos los demás subprocesos. Menos obvio, lo mismo puede suceder incluso si compila-threaded
y usa+RTS -N
opciones. El problema es que un subproceso no cooperativo puede privar al programador de tiempo de ejecución . Si en algún momento el subproceso no cooperativo es el único subproceso programado actualmente para ejecutarse, se volverá ininterrumpible y el programador nunca se volverá a ejecutar para considerar la programación de subprocesos adicionales, incluso si pudieran ejecutarse en otros subprocesos O / S.Si solo está tratando de probar algunas cosas, cambie la firma de
fib
afib :: Integer -> Integer
. ComoInteger
causa la asignación, todo comenzará a funcionar nuevamente (con o sin-threaded
).Si se encuentra con este problema en código real , la solución más fácil, con diferencia, es la sugerida por @Carl: si necesita garantizar la capacidad de interrupción de los subprocesos, se debe compilar el código
-fno-omit-yields
, lo que mantiene las llamadas del planificador en código no asignado . Según la documentación, esto aumenta los tamaños binarios; Supongo que también conlleva una pequeña penalización de rendimiento.Alternativamente, si el cálculo ya está dentro
IO
, entonces explícitamenteyield
en el bucle optimizado puede ser un buen enfoque. Para un cómputo puro, puede convertirlo a IO yyield
, aunque generalmente puede encontrar una manera simple de introducir una asignación nuevamente. En la mayoría de las situaciones realistas, habrá una manera de introducir solo unos "pocos"yield
o asignaciones, lo suficiente para que el hilo responda nuevamente, pero no lo suficiente como para afectar seriamente el rendimiento. (Por ejemplo, si tiene algunos bucles recursivos anidados,yield
o si fuerza una asignación en el bucle más externo).fuente
-fno-omit-yields
, ya que-O2
implica-fomit-yields
. downloads.haskell.org/~ghc/latest/docs/html/users_guide/…