这些线程会永远被阻塞吗?

时间:2020-06-09 16:26:44

标签: haskell concurrency

我目前正在阅读Simon Marlow的书“ Haskell中的并行和并发编程”,但我不理解此代码:

waitAny :: [Async a] -> IO a
waitAny as = do
  m <- newEmptyMVar
  let forkwait a = forkIO $ do r <- try (wait a); putMVar m r
  mapM_ forkwait as
  wait (Async m)

在这里,我们将putMVar调用N次,但只有1次等待操作。我是否理解尝试执行putMVar会阻止N-1个线程正确吗?这是怎么回事?

...或超级简化:

test = do
  m <- newEmptyMVar
  forkIO $ putMVar m 1
  forkIO $ putMVar m 2
  a <- readMVar m
  return a

为什么它可以正常工作?为什么我没有 Exception:线程在MVar操作中被无限期阻塞

1 个答案:

答案 0 :(得分:4)

Haskell中有关并发的一些基本规则:

  1. main线程退出时,它立即杀死所有其他线程。如果要给其他线程清理的机会,则必须明确地等待其他线程。

  2. 有一组特殊的异常(辅助线程(非主线程)丢弃),因此在未被捕获时不会打印它们:

    新创建的线程具有一个异常处理程序,该处理程序丢弃异常BlockedIndefinitelyOnMVarBlockedIndefinitelyOnSTMThreadKilled,并将所有其他异常传递给未捕获的异常处理程序。

    -Control.Concurrent.forkIO documentation

  3. 当线程等待MVar且无望取得任何进展时,它将收到异常。但是由于上述问题,在此示例中这是完全不可见的。注意,由于垃圾收集器的特殊支持,这种方式只能捕获非常简单的一类死锁。无法自动检测所有死锁。

在第二个示例中,主线程(假设main = test)在读取变量后立即退出,这没有时间让另一个线程(在putMVar上仍然被阻塞)响应(指向1以上)。因此,首先在主线程的末尾添加一个threadDelay,以便给另一个线程更多的时间。这还不足以带来区别,因为辅助线程被BlockedIndefinitelyOnMVar静默杀死(第2点)。在putMVar周围添加异常处理程序以产生显式输出。

import Control.Concurrent
import Control.Exception

main :: IO ()
main = do
  m <- newEmptyMVar :: IO (MVar Int)
  forkIO $ putMVar' m 1
  forkIO $ putMVar' m 2
  a <- readMVar m
  print a
  threadDelay 1000000      -- (1) Wait for other threads to clean up

putMVar' :: MVar Int -> Int -> IO ()
putMVar' r x =
  catch
    (putMVar r x)
    (\e ->
      putStrLn ("BLOCKED: " ++ show (x, e :: SomeException)))  -- (2) Print something if the thread dies because of a deadlock

{- Build this file with    ghc -threaded ThisFile.hs
   Run it with   ./ThisFile +RTS -N
-}

{- Output:

1
BLOCKED: (2,thread blocked indefinitely in an MVar operation)

-}

请注意,forkIO级别较低,因此通常应避免使用。从头开始实现同步需要大量的精力。异步库提供了更方便的抽象。


概述并从技术上回答您的问题:

在这里,我们将putMVar调用N次,但只有1次等待操作。我是否理解尝试执行putMVar会阻止N-1个线程正确吗?这是怎么回事?

这是正确的想法。在实践中,阻塞的线程将获得异常,因为垃圾回收器可以看到MVar不能从其他线程访问到,但是您不应该在生产中捕获并观察该异常,即使有可能,如图所示以上。确实,Control.Concurrent的文档说明了很多:

请注意,此功能仅用于调试,不应依赖于程序的正确操作。

-Control.Concurrent documentation


为什么它可以正常工作?为什么我没有Exception: thread blocked indefinitely in an MVar operation

实际上会有这样的例外,但是:

  1. main线程退出太快,以至于实际上也无法退出;

  2. 当非main线程被BlockedIndefinitelyOnMVar杀死时,它们不会显示异常,您必须自己这样做。