当MVar被垃圾收集时杀死一个线程

时间:2012-06-03 14:51:06

标签: haskell concurrency garbage-collection

我有一个工作线程,它从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 个答案:

答案 0 :(得分:27)

它只会工作:当MVar只能被阻塞的线程访问时,线程会发送BlockedIndefinitelyOnMVar异常,这通常会导致它无声地死亡(线程的默认异常处理程序忽略此异常。)

顺便说一下,当线程死亡时进行一些清理时,你需要使用forkFinally(我just addedControl.Concurrent)。

答案 1 :(得分:22)

如果你很幸运,你会得到一个"BlockedIndefinitelyOnMVar",表示你正在等待没有线程写过的MVar。

但是,引用Ed Yang的话,

  

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"

答案 2 :(得分: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 尽管有所期待,但仍然将gar保持在ghc-7.4.1上。

答案 3 :(得分:1)

虽然BlockedIndefinitelyOnMVar应该有用,但也请考虑使用ForeignPointer finalizers。这些的正常作用是删除Haskell中不再可访问的C结构。但是,您可以将任何IO终结器附加到它们。