34

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

如果幸运的话,你会得到一个"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"
于 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 并没有使 MVar 在 ghc-7.4.1 上保持活力。

于 2012-06-06T10:50:32.990 回答
1

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

于 2012-06-08T00:38:01.473 回答