2012-06-03 2 views
33

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はもはや有効範囲にありません。どうやってするか?

+1

MVarの弱いリファレンスをここで使用できますか? –

+0

@ JohnL:確かにスレッドがMVarを待つ時間を制限し、ウィークポインタがまだ生存しているかどうかを定期的にチェックするとどうなりますか? (弱ポインタ​​は、おそらくMVarと同時にスコープから外れる別の値を指し示す必要があります)理想的には、ガベージコレクションの頻度で期間を指定するのが理想的です。 –

+0

@HeinrichApfelmus:私の最初の編集は基本的にこれを一つの方法で行います。他のスレッドを生成して、ブロックされたスレッドで非同期例外を発生させ、読み込みを中断し、弱いポインタからの再読み込みを強制することをお勧めします。私は今日後でいくつかのコードを試してみるつもりです。しかし、これはまだ、donsの技術で簡単に解決されるもののために、言語の悪名高い部分を使用して、多くの作業のように思えます。 –

答えて

26

それだけで動作します:MVarがそれにブロックされたスレッドによってのみ到達可能であるとき、そのスレッドは、通常、それがスレッド無視のために静かに(デフォルトの例外ハンドラを死ぬことになりますBlockedIndefinitelyOnMVar例外を、送信されますこの例外)。

ところで、スレッドが消えたときにクリーンアップを行うには、forkFinally(これはI just addedControl.Concurrent)を使用します。

+0

素晴らしい!スレッドで 'BlockedIndefinitelyOnMVar'例外を捕まえることができますか? –

+0

もちろん、BlockedIndefinitelyOnMVarは単なる普通の例外です。 –

+2

これは、MVarが複数のスレッドから到達可能ですが、すべてがブロックされている場合に当てはまりますか?また、GCの観点からは、TSOがMVarでブロックされている場合、ライブセットから削除され、MVar経由でしか到達できないことを意味しますか? –

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" 
+1

ああ、私は見るので、私は手でスレッドを殺す必要があります。 (私もそれに例外をスローすることができます)。私は本当にやりたいことを明確にするべきでしょう。すなわち、スレッドの寿命を価値のあるものに結びつけたい、つまり、スレッドを、 'readFile'の結果のガベージコレクションがファイルを閉じるべきように、MVarのガベージコレクションの際に解放されるリソースとみなしたいと考えました。 –

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 
* 

だからが期待にもかかわらず、GHC-7.4.1に生きMVARを保持しませんでした takeMVarに遮断します。

+0

ニース!したがって、コードでは、スレッドがまだMVarを待っている間にファイナライザが実行されていることを明確に示しています。 'takeMVar'は、MVarの内部表現を、"外側の船体 "がガベージコレクションできる点まで解体するように見えます。 'data Lazy a = Lazy a'を使い、' MVar String'の代わりに 'Lazy(MVar String)'にファイナライザを置くことで、どのようなデータ構造でも同様の効果が得られます。 –

+0

ええ、私もそれを理解しました。違いはaddMVarFinalizerとaddFinalizerの違いです。 MVarに入れられた最後の項目は回復できないかもしれませんが、これは大丈夫です。 –

1

BlockedIndefinitelyOnMVarが機能するはずですが、ForeignPointer finalizersも使用することを検討してください。これらの通常の役割は、HaskellでアクセスできないC構造を削除することです。ただし、任意のIOファイナライザを接続することができます。

+0

私が正しく覚えていれば、彼らにIOアクションを付けることはできません。それはCファイナライザでなければなりません。まだそれは本当ですか? –

+0

"正しいタイプのFunPtrを生成するために宣言されたラッパースタブを使用して作成されたHaskell関数へのポインター。" [Foreign.Ptr](http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/Foreign-Ptr.html#t%3AFunPtr) – dmbarbour

+0

それは簡単ではありません。 http://www.haskell.org/ghc/docs/latest/html/users_guide/ffi-ghc.html –

関連する問題