使用 MVar 在线程之间进行通信

Control.Concurrent 中使用 MVar a 类型及其附带函数在线程之间传递信息非常容易:

  • newEmptyMVar::IO (MVar a) - 创造一个新的 MVar a
  • newMVar::a -> IO (MVar a) - 创建一个具有给定值的新 MVar
  • takeMVar::MVar a -> IO a - 从给定的 MVar 中检索值,或阻塞直到有一个可用
  • putMVar::MVar a -> a -> IO () - 将给定值放入 MVar,或阻塞直到它为空

让我们在一个线程中将 1 到 1 亿的数字相加并等待结果:

import Control.Concurrent
main = do
  m <- newEmptyMVar
  forkIO $ putMVar m $ sum [1..10000000]
  print =<< takeMVar m  -- takeMVar will block 'til m is non-empty!

更复杂的演示可能是在等待更多输入时在后台获取用户输入和求和:

main2 = loop
  where 
    loop = do
        m <- newEmptyMVar
        n <- getLine
        putStrLn "Calculating. Please wait"
        -- In another thread, parse the user input and sum
        forkIO $ putMVar m $ sum [1..(read n::Int)]
        -- In another thread, wait 'til the sum's complete then print it
        forkIO $ print =<< takeMVar m
        loop

如前所述,如果你调用 takeMVar 并且 MVar 是空的,它会阻塞,直到另一个线程将某些东西放入 MVar,这可能会导致餐饮哲学家的问题 。同样的事情发生在 putMVar:如果它已满,它会阻止’直到它是空的!

采取以下功能:

concurrent ma mb = do
  a <- takeMVar ma
  b <- takeMVar mb
  putMVar ma a
  putMVar mb b

我们用一些 MVars 运行这两个函数

concurrent ma mb     -- new thread 1 
concurrent mb ma     -- new thread 2

可能发生的是:

  1. 线程 1 读取 ma 并阻止 ma
  2. 线程 2 读取 mb,因此阻止 mb

现在线程 1 无法读取 mb,因为线程 2 已阻止它,并且线程 2 无法读取 ma,因为线程 1 阻止了它。经典的僵局!