Ste*_*orn 6 concurrency haskell
我目前正在阅读 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)
Run Code Online (Sandbox Code Playgroud)
这里我们调用 putMVar N 次,但我们只有 1 次等待操作。我是否理解 N-1 个线程在尝试执行 putMVar 时会被阻塞?这里发生了什么?
...或超级简化:
test = do
m <- newEmptyMVar
forkIO $ putMVar m 1
forkIO $ putMVar m 2
a <- readMVar m
return a
Run Code Online (Sandbox Code Playgroud)
为什么它可以毫无问题地工作?为什么我没有异常:线程在 MVar 操作中被无限期阻塞?
Haskell 中关于并发的一些基本规则:
当main
线程退出时,它会立即杀死所有其他线程。如果您想让其他线程有机会进行清理,则必须显式等待它们。
辅助(非主)线程会丢弃一组特定的异常,因此当它们未被捕获时不会打印它们:
新创建的线程有一个异常处理程序,它丢弃异常
BlockedIndefinitelyOnMVar
、BlockedIndefinitelyOnSTM
和ThreadKilled
,并将所有其他异常传递给未捕获的异常处理程序。
当一个线程等待一个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)
-}
Run Code Online (Sandbox Code Playgroud)
请注意,forkIO
通常应该避免,因为它的级别很低。从头开始实现同步需要付出很大的努力。异步库提供了更方便的抽象。
概括并从技术上回答您的问题:
这里我们调用 putMVar N 次,但只有 1 次等待操作。我理解正确吗:尝试执行 putMVar 时 N-1 个线程将被阻塞?这里发生了什么?
这是正确的想法。实际上,被阻止的线程将收到异常,因为垃圾收集器可以看到MVar
无法从其他线程访问该异常,但您不应该在生产中捕获并观察该异常,即使有可能如上所示。事实上,文档Control.Concurrent
说明了这一点:
请注意,此功能用于调试,不应依赖该功能来保证程序的正确运行。
为什么它可以毫无问题地工作?为什么我没有
Exception: thread blocked indefinitely in an MVar operation
?
其实也会有这样的异常,但是:
线程main
退出太快,以至于实际发生的情况无法实现;
当非main
线程被杀死时BlockedIndefinitelyOnMVar
,它们不会打印异常,你必须自己这样做。