简而言之:
mkfifo fifo; (echo a > fifo) &; (echo b > fifo) &; cat fifo
我所期望的:
a
b
因为第一个echo … > fifo
应该是第一个打开文件的,所以我希望该进程是第一个写入它的进程(首先打开它的解锁)。
我得到什么:
b
a
令我惊讶的是,这种行为也发生在打开两个单独的终端以在绝对独立的进程中进行写入时。
我是否误解了命名管道的先进先出语义?
斯蒂芬建议添加延迟:
#!/usr/bin/zsh
delay=$1
N=$(( $2 - 1 ))
out=$(for n in {00..$N}; do
mkfifo /tmp/fifo$n
(echo $n > /tmp/fifo$n) &
sleep $delay
(echo $(( $n + 1000 )) > /tmp/fifo$n )&
# intentionally using `cat` here to not step into any smartness
cat /tmp/fifo$n | sort -C || echo +1
rm /tmp/fifo$n
done)
echo "$(( $res )) inverted out of $(( $N + 1 ))"
现在,这 100% 正确(delay = 0.1, N = 100
)。
尽管如此,mkfifo fifo; (echo a > fifo) &; sleep 0.1 ; (echo b > fifo) &; cat fifo
手动运行几乎总是会产生相反的顺序。
事实上,即使复制和粘贴for
循环本身也有一半的时间会失败。我对这里发生的事情感到非常困惑。
这与管道的 FIFO 语义无关,也无法证明它们的任何内容。这与 FIFO 在打开时阻塞直到它们被打开以进行写入和读取的事实有关。
cat
所以在打开fifo
阅读之前什么都不会发生。在后台启动进程意味着您不知道它们何时实际被调度,因此不能保证第一个后台进程会在第二个后台进程之前完成它的工作。这同样适用于解除阻塞进程。
您可以通过人为地延迟第二个进程来提高几率,同时仍然使用后台进程:
延迟时间越长,几率越大:
echo a > fifo
等待完成打开的块fifo
,cat
启动并打开fifo
哪些解锁echo a
,然后echo b
运行。然而,这里的主要因素是何时
cat
打开 FIFO:在此之前,shell 会阻止尝试设置重定向。最终看到的输出顺序取决于写入过程被畅通的顺序。如果你
cat
先运行,你会得到不同的结果:这样一来,
fifo
写作的开放往往不会阻塞(仍然,没有保证),所以你会首先看到a
比第一次设置更高的频率。您还将看到运行cat
前完成,即仅输出。echo b
a
管道是先进先出的。你的问题是你误解了“in”何时发生。“in”事件是写,而不是打开。
删除无用的标点符号,您的代码是:
echo a > fifo
这会并行运行命令echo b > fifo
。无论谁先进来,都会先出去,但对于谁先进来,这是一场大致相等的竞赛。如果你想
a
先被阅读b
,你必须安排先写b
。这意味着您必须等到echo a > fifo
完成后才能开始echo b > fifo
。如果你想进一步挖掘,你需要区分底层发生的基本操作。
echo a > fifo
结合了三个操作:fifo
写作。a
和一个换行符)写入文件。您可以安排这些操作在不同时间发生:
同样,
cat foo
结合了打开、读取和关闭操作。您可以将它们分开:(
read
内置的 shell 实际上可能会进行多个read
系统调用,但现在这并不重要。)Fifos 实际上并不完全是管道。它们更像是潜在的管道。fifo是一个目录条目,当进程打开fifo进行读取时,会创建一个管道对象。如果进程在不存在管道时打开 fifo 进行写入,则
open
调用会阻塞,直到创建管道为止。此外,如果进程打开fifo进行读取,此操作也会阻塞,直到进程打开fifo进行写入(除非读取器以非阻塞模式打开管道,这在shell中不方便)。因此,命名管道上的第一个开放读取和第一个开放写入将同时返回。这是一个将这些知识付诸实践的 shell 脚本。
注意两个开口是如何同时发生的(尽可能接近我们可以观察到的)。写只能在那之后发生。
注意:上面的脚本中实际上存在竞争条件——但它与管道无关。这些
echo >&2
命令正在争先恐后cat >&2
地写入终端,因此您可能会a
从cat
之前看到opening for writing
和wrote a
. 如果您想更精确地查看时间,您可以跟踪系统调用。例如,在 Linux 下:现在,如果您放置两个写入器,两个写入器都会在开始步骤阻塞,直到读取器到达。谁先开始调用并不重要
open
:管道对于数据是先进先出的,而不是对于打开的尝试。谁先写才是最重要的。这是一个试验这个的脚本。使用两个参数调用脚本:reader a 的等待时间和 writer b 的等待时间。阅读器在 0.2 秒后上线。如果两个等待时间都小于 0.2 秒,那么两个写入器都会在写入器上线后立即尝试写入,这是一场竞赛。另一方面,如果等待时间大于 0.2,则先到者先得到输出。