我希望有人可以帮助我理解以下代码为何生成下面的输出。该代码来自Simon Marlow的书的“并发性”一章(下面的链接)。

基于对各种函数的描述,我假设第二个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'

最佳答案

就我自己而言,这是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

我们拥有的是一个后台线程和一个运行在较小内存(称为MVarm)上的主线程同时运行。
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

如我们所见,阻止BGMVar中放置比MN可以读取的值更多的值。这将产生您观察到的打印语义。

09-25 19:58