我希望有人可以帮助我理解以下代码为何生成下面的输出。该代码来自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
我们拥有的是一个后台线程和一个运行在较小内存(称为
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
可以读取的值更多的值。这将产生您观察到的打印语义。