Quero escrever um programa cujo thread principal bifurque um novo thread para computação e aguarde que ele termine por um período de tempo. Se o encadeamento filho não for concluído no tempo determinado, o tempo limite será excedido e eliminado. Eu tenho o seguinte código para isso.
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'
Compilei o programa acima com ghc -O2 Test.hs
e ghc -O2 -threaded Test.hs
executei-o, mas em ambos os casos o programa trava sem imprimir nada ou sair. Se eu adicionar um threadDelay (2 * 1000 * 1000)
ao thread de computação antes do if
bloco, o programa funcionará conforme o esperado e será concluído após um segundo, pois o thread do timer poderá preencher o mvar
.
Por que o encadeamento não está funcionando como eu esperava?
multithreading
haskell
concurrency
timeout
blocking
Cara aleatória
fonte
fonte
MVar
afirmam que é suscetível a condições de corrida. Eu levaria essa nota a sério.MVar
disciplina me parece boa aqui.+RTS -N
? verifique wiki.haskell.org/Concurrency para obter mais informaçõesRespostas:
O GHC usa uma espécie de híbrido de multitarefa cooperativa e preventiva em sua implementação simultânea.
No nível Haskell, parece preventivo porque os threads não precisam render explicitamente e podem ser aparentemente interrompidos pelo tempo de execução a qualquer momento. Porém, no nível do tempo de execução, os threads "cedem" sempre que alocam memória. Como quase todos os threads do Haskell estão constantemente alocando, isso geralmente funciona muito bem.
No entanto, se um cálculo específico puder ser otimizado para código não alocado, ele poderá se tornar não cooperativo no nível de tempo de execução e, portanto, imprevisível no nível Haskell. Como o @Carl apontou, na verdade é a
-fomit-yields
bandeira, que está implícita-O2
que permite que isso aconteça:Obviamente, no tempo de execução de thread único (sem
-threaded
sinalizador), isso significa que um thread pode eliminar completamente todos os outros threads. Menos obviamente, a mesma coisa pode acontecer mesmo se você compilar-threaded
e usar+RTS -N
opções. O problema é que um encadeamento não cooperativo pode deixar de fora o próprio planejador de tempo de execução . Se, em algum momento, o encadeamento não cooperativo for o único agendado atualmente para execução, ele se tornará ininterrupto e o planejador nunca será executado novamente para considerar o agendamento de encadeamentos adicionais, mesmo que eles possam ser executados em outros encadeamentos O / S.Se você está apenas tentando testar algumas coisas, altere a assinatura de
fib
parafib :: Integer -> Integer
. Já queInteger
causa a alocação, tudo começará a funcionar novamente (com ou sem-threaded
).Se você se deparar com esse problema no código real , a solução mais fácil, de longe, é a sugerida pelo @Carl: se você precisar garantir a interrupção dos encadeamentos, o código deve ser compilado
-fno-omit-yields
, o que mantém as chamadas do agendador no código não alocado . Conforme a documentação, isso aumenta os tamanhos binários; Suponho que também venha com uma pequena penalidade de desempenho.Como alternativa, se a computação já estiver dentro
IO
, então explicitamenteyield
no loop otimizado pode ser uma boa abordagem. Para um cálculo puro, você pode convertê-lo em E / S eyield
, embora normalmente possa encontrar uma maneira simples de introduzir uma alocação novamente. Na maioria das situações realistas, haverá uma maneira de introduzir apenas algunsyield
ou alocações - o suficiente para tornar o thread responsivo novamente, mas não o suficiente para afetar seriamente o desempenho. (Por exemplo, se você tiver alguns loops recursivos aninhadosyield
ou forçar uma alocação no loop mais externo.)fonte
-fno-omit-yields
, uma vez que-O2
implica-fomit-yields
. downloads.haskell.org/~ghc/latest/docs/html/users_guide/…