`forkIO`和`putMVar`:引擎盖下发生了什么?

时间:2014-11-11 01:39:11

标签: haskell concurrency mutable

我希望有人可以帮助我理解为什么下面的代码生成下面的输出。代码来自Simon Marlow的书中的Concurrency章节(链接如下)。

基于各种函数的描述,我假设第二个putMVar函数应该被阻塞,因为(i)两个putMVar函数都是同一个线程的一部分,(ii)一个值已被分配。显然情况并非如此。很高兴在这里了解“引擎盖下”发生了什么。

(注意:本书使用do表示法,但我更喜欢>>=表示法,因为我认为它更直接 - 因此下面的代码版本。)

Link to book

import Control.Concurrent

main :: IO ()
main = newEmptyMVar >>=
       \m -> forkIO (putMVar m 'x' >>= \_ -> putMVar m 'y') >>=
             \_ -> takeMVar m >>=
                   print >>=
                   \_ -> takeMVar m >>=
                         print

上面代码的输出:

% ./mvar2
'x'
'y'

1 个答案:

答案 0 :(得分:10)

为了我自己,这里的代码是do符号。

main :: IO ()
main = do
  m <- newEmptyMVar
  forkIO $ do
    putMVar m 'x'
    putMVar m 'y'
  x <- takeMVar m
  print x
  y <- takeMVar m
  print y

我们拥有的是后台线程,主线程同时在一小块内存上进行通信,MVar称为m

MVar语义是这样的:MVar可以为空或满。如果您想阅读MVar并且它是空的,那么您必须等到它变满。如果您readMVar,那么您只需尽快解决存储在完整MVar中的值。如果您takeMVar,那么您将解析该值,然后在阅读后立即清空

另一方面,当您putMVar将新值添加到MVar时,如果MVar为空,您将立即成功。如果已满,则必须等到它变空。

由于在读取和写入方面等待,因此线程变得同步MVar的空虚和充实。

因此,在这个例子中,我们可以想象许多可能的线性化故事,以了解执行的进展情况。幸运的是,它们的工作方式相同。让我们调用后台线程BG和主线程MN

t = 1  :  MN makes a new, empty MVar called 'm'
t = 2  :  BG puts 'x' in 'm' making it full
t = 3  :  BG attempts to put 'y' in 'm', but since 'm' is full BG blocks
t = 4  :  MN attempts to read 'm' and succeeds as it is full
t = 5  :  BG now places 'y' into the newly empty 'm'
t = 6  :  BG dies
t = 6  :  MN prints the value it previously read
t = 7  :  MN attempts to read 'm' and succeeds as it is full
t = 8  :  MN prints the value it previously read
t = 9  :  MN dies

我们可以看到,BG无法在MVar中放置比MN可以读取的值更多的值。这会产生您观察到的印刷语义。