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

ice*_*man 6 concurrency haskell mutable

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

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

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

链接到书

import Control.Concurrent

main :: IO ()
main = newEmptyMVar >>=
       \m -> forkIO (putMVar m 'x' >>= \_ -> putMVar m 'y') >>=
             \_ -> takeMVar m >>=
                   print >>=
                   \_ -> takeMVar m >>=
                         print
Run Code Online (Sandbox Code Playgroud)

输出上面的代码:

% ./mvar2
'x'
'y'
Run Code Online (Sandbox Code Playgroud)

J. *_*son 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
Run Code Online (Sandbox Code Playgroud)

我们所拥有的是后台线程和主线程并行运行通过一小段内存进行通信,被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
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,BG是从把更多的价值在预防MVar比什么MN可以读取.这会产生您观察到的印刷语义.