forkIO/killThread と forkProcess の相互作用
-
21-12-2019 - |
質問
以下のコードを書いて気づいたのですが、 killThread
ブロックされてもスレッドは継続します。これは forkProcess で実行した場合にのみ発生します。forkProcess を削除すると、すべてが期待どおりに動作します。
コード
{-# LANGUAGE TupleSections #-}
module Main where
import Control.Concurrent
import Control.Monad
import System.Posix.Process
{-# NOINLINE primes #-}
primes :: [Integer]
primes = 2:[x | x <- [3..], all (not . flip isDivisorOf x) (takeWhile (< truncate (sqrt $ fromInteger x :: Double)) primes)]
where x `isDivisorOf` y = y `rem` x == 0
evaluator :: Show a => [a] -> IO ()
evaluator xs = do
putStrLn "[Evaluator] Started evaluator."
forM_ xs $ \x -> putStrLn $ "[Evaluator] Got result: " ++ show x
putStrLn "[Evaluator] Evaluator exited."
test :: IO ThreadId
test = forkIO (evaluator $ filter ((== 13) . flip rem (79 * 5 * 7 * 3 * 3 * 2 * 3)) primes) -- Just some computation that doesn't finsish too fast
main :: IO ()
main = do
pid <- forkProcess $ do
a <- test
threadDelay $ 4000 * 1000
putStrLn "Canceling ..."
killThread a
putStrLn "Canceled"
void $ getProcessStatus True False pid
出力
$ ghc test.hs -O -fforce-recomp -threaded -eventlog -rtsopts # I also tried with -threaded
$ ./test +RTS -N2 # I also tried without -N
[Evaluator] Started evaluator.
[Evaluator] Got result: 13
[Evaluator] Got result: 149323
[Evaluator] Got result: 447943
[Evaluator] Got result: 597253
[Evaluator] Got result: 746563
[Evaluator] Got result: 1045183
Canceling ...
[Evaluator] Got result: 1194493
[Evaluator] Got result: 1642423
[Evaluator] Got result: 1791733
[Evaluator] Got result: 2090353
[Evaluator] Got result: 2687593
[Evaluator] Got result: 3135523
[Evaluator] Got result: 3284833
[Evaluator] Got result: 4777933
[Evaluator] Got result: 5375173
^C[Evaluator] Got result: 5524483
^C
これは、メモリ割り当てがないために GHC のスレッド スケジューラが実行されないというよくある問題ではありません。プログラムを実行してそれを確認しました +RTS -sstderr
, これは、ガベージ コレクターが非常に頻繁に実行されていることを示しています。Linux 64ビットでこれを実行しています。
解決
これ バグレポート とメモしています forkProcess
ドキュメントにそのような記載がないにもかかわらず、子プロセスの非同期例外をマスクします。この動作は 7.8.1 のリリース時に修正されるはずです。
もちろん、非同期例外がマスクされている場合、 throw
の中で killThread
無期限にブロックします。単純に行を削除すると、 main
含む forkProcess
そして getProcessStatus
, 、プログラムは意図したとおりに動作します。
module Main where
import Control.Concurrent
import Control.Monad
import System.Posix.Process
{-# NOINLINE primes #-}
primes :: [Integer]
primes = 2:[ x | x <- [3..], all (not . flip isDivisorOf x) (takeWhile (< truncate (sqrt $ fromInteger x :: Double)) primes)]
where x `isDivisorOf` y = y `rem` x == 0
evaluator :: Show a => [a] -> IO ()
evaluator = mapM_ $ \x ->
putStrLn $ "[Evaluator] Got result: " ++ show x
test :: IO ThreadId
test = forkIO (evaluator $ filter ((== 13) . flip rem (79 * 5 * 7 * 3 * 3 * 2 * 3)) primes) -- Just some computation that doesn't finsish too fast
main :: IO ()
main = do
a <- test
threadDelay $ 4000 * 1000
putStrLn "Canceling ..."
killThread a
putStrLn "Canceled"
で構築します ghc --make -threaded async.hs
そして一緒に走ります ./async +RTS -N4
.
何らかの理由で別のプロセスが必要な場合は、GHC 7.6.3 の子プロセスの非同期例外のマスクを手動で解除する必要があります。
所属していません StackOverflow