34

MVar から繰り返しデータを読み取り、その上でいくつかの有用な作業を実行するワーカー スレッドがあります。しばらくすると、プログラムの残りの部分はそのワーカー スレッドを忘れます。つまり、空の MVar で待機し、非常に孤独になります。私の質問は:

スレッドが MVar に書き込みを行わなくなった場合、MVar はガベージ コレクションの対象になりますか? ガベージ コレクションは待機中のスレッドを強制終了しますか? どちらでもない場合、MVar をガベージ コレクションしてスレッドを強制終了する必要があることをコンパイラに示すことはできますか?

編集:おそらく質問の目的を明確にする必要があります。デッドロックに対する一般的な保護は望んでいません。代わりに、私がやりたいことは、ワーカー スレッドの寿命を値の寿命に結びつけることです (たとえば、死んだ値はガベージ コレクションによって要求されます)。つまり、ワーカー スレッドは、手動ではなく、特定の値 (MVar または派生物) がガベージ コレクションされたときに解放したいリソースです。


ここに、私が考えていることを示すサンプルプログラムがあります

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"

つまり、スレッドと通信できなくなったとき、つまり、通信に使用された MVar がスコープ外になったときに、スレッドを強制終了したいと考えています。どうやってするか?

4

4 に答える 4

27

それはうまくいきます:MVarブロックされているスレッドだけが に到達できる場合、スレッドはBlockedIndefinitelyOnMVar例外を送信され、通常はサイレントに終了します (スレッドのデフォルトの例外ハンドラーはこの例外を無視します)。

ところで、スレッドが終了したときにいくつかのクリーンアップを行うには、使用する必要がありますforkFinally(これはに追加したばかりControl.Concurrentです)。

于 2012-06-07T19:26:18.907 に答える
22

運が良ければ、スレッドが書き込みを行わない MVar を待機していることを示す"BlockedIndefinitelyOnMVar"を取得できます。

しかし、エド・ヤンの言葉を引用すると、

GHC は、スレッドへの参照がない場合にのみ、そのスレッドがガベージと見なされる可能性があることを認識しています。スレッドへの参照を保持しているのは誰ですか? MVar は、スレッドがこのデータ構造をブロックしており、このブロック リストに自身を追加したためです。MVar を存続させているのは誰ですか? これは、takeMVar の呼び出しを含むクロージャーです。だからスレは残る。

ちょっとした作業がなければ (ちなみに、これを見るのは非常に興味深いことです)、BlockedIndefinitelyOnMVar は、Haskell プログラムにデッドロック保護を提供するための明らかに有用なメカニズムではありません。

GHC は、スレッドが進行するかどうかを知るという一般的な問題を解決できません。

より良いアプローチは、スレッドにDoneメッセージを送信して明示的にスレッドを終了することです。たとえば、メッセージの種類を、メッセージの終わりの値も含むオプションの値に持ち上げるだけです。

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

そして、正しいクリーンアップを取得します。

$ ./A
"Haskell"
"42"
"Done!"
"Still here"
于 2012-06-03T15:18:43.463 に答える
11

単純な弱い MVar をテストしたところ、ファイナライズされて強制終了されました。コードは次のとおりです。

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

プログラムとのセッションは次のとおりです。

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

そのため、takeMVarをブロックしても、予想に反して ghc-7.4.1 でMVar が存続しませんでした。

于 2012-06-06T10:50:32.990 に答える
1

動作するはずですが、 ForeignPointer finalizersBlockedIndefinitelyOnMVarの使用も検討してください。これらの通常の役割は、Haskell でアクセスできなくなった C 構造を削除することです。ただし、任意の IO ファイナライザーをそれらにアタッチできます。

于 2012-06-08T00:38:01.473 に答える