我有一个工作线程,它从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不再在范围内时。怎么做?
答案 0 :(得分:27)
它只会工作:当MVar
只能被阻塞的线程访问时,线程会发送BlockedIndefinitelyOnMVar
异常,这通常会导致它无声地死亡(线程的默认异常处理程序忽略此异常。)
forkFinally
(我just added到Control.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终结器附加到它们。