使用 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 阻止了它。經典的僵局!