Подтвердить что ты не робот

Убийство нити при сборке мусора 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, используемый для связи, больше не находится в области видимости. Как это сделать?

4b9b3361

Ответ 1

Это просто сработает: когда MVar доступен только для потока, который заблокирован на нем, тогда поток отправляется исключение BlockedIndefinitelyOnMVar, которое обычно приводит к его молчанию (обработчик исключений по умолчанию для нить игнорирует это исключение).

BTW, для выполнения какой-либо очистки, когда поток умирает, вы хотите использовать forkFinally (который я просто добавил в Control.Concurrent).

Ответ 2

Если вам повезет, вы получите "BlockedIndefinitelyOnMVar" , указав, что вы ожидаете на MVar, что ни один поток не будет когда-либо пишите.

Но, цитируя Эд Янга,

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"

Ответ 3

Я протестировал простой слабый 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, несмотря на ожидания.

Ответ 4

Пока BlockedIndefinitelyOnMVar должен работать, также рассмотрите возможность использования финализаторов ForeignPointer. Их нормальная роль заключается в удалении структур C, которые больше недоступны в Haskell. Однако вы можете присоединить к ним любой финализатор ввода-вывода.