管道进料异常
Pipe Feeding Anomaly
我有一个 gzip 文件,我将其分成 3 个单独的文件:xaa、xab、xac。我做了一个 fifo
mkfifo p1
并通过读取文件重新组合文件,同时计算校验和并将文件解压缩到管道中:
cat p1 p1 p1 | tee >(sha1sum > sha1sum_new.txt) | gunzip > output_file.txt
如果我用
从另一个终端馈送管道,这就很好用了
cat xaa > p1
cat xab > p1
cat xac > p1
但是如果我用一根管子喂管道,
cat xaa > p1; cat xab > p1; cat xac > p1
接收管道挂起,未生成校验和,虽然生成了输出文件,但它被截断了 - 但截断了小于最终文件大小的量。
为什么第二种情况下的行为与第一种情况不同?
我不是很肯定,但我认为这涉及到竞争条件。考虑将其用作更简单的替代方案:
tee >(sha1sum > sha1sum_new.txt) < p1 | gunzip > output_file.txt
并使用单个命令
喂养 p1
cat xaa xab xac > p1
这样,您打开 p1
只写一次,打开它只读一次。
有趣的问题。正如另一个答案所提到的,你有一个竞争条件——我很确定这一点。事实上,在这两种情况下您都有竞争条件,但在前者中您很幸运它不会发生,因为您的文件可能很小并且可以在您输入下一个命令行之前读取。请允许我解释一下。
那么,首先介绍一下背景知识:
cat
按顺序打开您作为参数提供给它的每个文件,将其打印到输出,然后关闭文件并移至下一个文件。 cat
是按顺序打开每个文件还是先打开所有文件然后按顺序写入每个文件的确切细节可能会有所不同,但这与讨论无关。在这两种情况下,您都会遇到竞争条件
open(2)
系统调用将阻塞在 FIFO / 管道上,直到另一端打开。因此,例如,如果进程 pid1
打开 FIFO 进行读取,open(2)
将阻塞直到 pid2
打开 FIFO 进行写入。换句话说,打开一个没有活动 reader 或写入器的 FIFO 会隐式同步两个进程并保证进程不会从还没有写入器的管道中读取,或者写入器不会写入管道还没有 reader。但是正如我们将看到的,这将是有问题的。
到底发生了什么
当你这样做时:
cat xaa > p1
cat xab > p1
cat xac > p1
事情真的很慢,因为人类很慢。输入第一行后,cat
打开 p1
进行写入。另一个 cat
在打开它进行阅读时被阻止(或者可能还没有,但让我们假设它是)。一旦两个 cat
进程都打开 p1
- 一个用于写入,另一个用于读取 - 数据开始流动。
然后,在您甚至有机会输入下一个命令行 (cat xab >p1
) 之前,整个文件都流过管道,大家都很高兴 - cat
reader 进程发现管道上的文件结束,调用 close(2)
,cat
编写器完成写入文件,并关闭 p1
。 cat
reader 移动到下一个文件(又是 p1
),打开它,然后阻塞,因为还没有活动的作者打开 fifo。
然后,你,慢人,输入下一个命令行,这会导致另一个 cat
writer 进程打开 FIFO,这会解锁另一个正在等待打开以供读取的 cat
,一切都会再次发生。然后再次用于第三个命令行。
当您将所有内容都放在 shell 的一行中时,事情发生得太快了。
让我们区分 3 个 cat
调用。将其命名为 cat1
、cat2
和 cat3
:
cat1 xaa > p1; cat2 xab > p1; cat3 xac > p1
shell按顺序执行每条命令,等待上一条命令完成后再转到下一条。
然而,可能只是 cat1
将所有内容写入 p1
并退出,shell 移动到 cat2
,这会打开 FIFO然后又开始写p1
的内容,cat
reader本来就没来得及读完cat1
写的内容,现在突然cat
reader "thinks" 它仍在从第一个文件(第一个 p1
)读取,但在某个时候它开始读取 cat2
开始推入的数据管道(就像在第一个 p1
中一样)。如果cat2
更快,它无法知道第一个"copy"的数据已经结束,并且在cat
reader完成读取之前打开FIFO cat1
写道。
是的,很微妙,但这正是正在发生的事情。
然后,当然,输入最终也结束了,cat
reader会认为第一个p1
已经完成了,然后移到下一个[=19= 】,打开等待下一位作者打开。但永远不会有下一个作家!它永远阻塞,整个管道永远停滞不前。
如何修复
其他答案中的解决方案解决了问题。您在评论中提到这对您来说可能还不够,因为您无法控制新作者何时以及如何打开和使用管道。
所以我建议改为:
- 创建一个持久写入进程,它只维护 FIFO 为写入而打开,即使它永远不会真正写入。这只是为了确保没有 window 没有作者处于活动状态并且 reader 尝试读取的时间。为此,只需
cat
在后台向 p1
标准输入:cat >p1 &
。完成后,终止后台作业。
- 只在reader进程中打开管道一次。这可以通过
cat p1 | tee >(sha1sum ...)
或使用其他答案 (tee >(...) <p1
) 中提出的方法来完成。毕竟,无论您的系统多么复杂,打开一次 FIFO 就足够了; FIFO 本质上总是以先进先出的方式为您提供数据。
保留后台 cat
编写器 运行 只要您知道有新文件到达的机会/新编写器打开 FIFO 并使用它。不要忘记在知道输入结束时终止后台作业。
我有一个 gzip 文件,我将其分成 3 个单独的文件:xaa、xab、xac。我做了一个 fifo
mkfifo p1
并通过读取文件重新组合文件,同时计算校验和并将文件解压缩到管道中:
cat p1 p1 p1 | tee >(sha1sum > sha1sum_new.txt) | gunzip > output_file.txt
如果我用
从另一个终端馈送管道,这就很好用了cat xaa > p1
cat xab > p1
cat xac > p1
但是如果我用一根管子喂管道,
cat xaa > p1; cat xab > p1; cat xac > p1
接收管道挂起,未生成校验和,虽然生成了输出文件,但它被截断了 - 但截断了小于最终文件大小的量。
为什么第二种情况下的行为与第一种情况不同?
我不是很肯定,但我认为这涉及到竞争条件。考虑将其用作更简单的替代方案:
tee >(sha1sum > sha1sum_new.txt) < p1 | gunzip > output_file.txt
并使用单个命令
喂养p1
cat xaa xab xac > p1
这样,您打开 p1
只写一次,打开它只读一次。
有趣的问题。正如另一个答案所提到的,你有一个竞争条件——我很确定这一点。事实上,在这两种情况下您都有竞争条件,但在前者中您很幸运它不会发生,因为您的文件可能很小并且可以在您输入下一个命令行之前读取。请允许我解释一下。
那么,首先介绍一下背景知识:
cat
按顺序打开您作为参数提供给它的每个文件,将其打印到输出,然后关闭文件并移至下一个文件。cat
是按顺序打开每个文件还是先打开所有文件然后按顺序写入每个文件的确切细节可能会有所不同,但这与讨论无关。在这两种情况下,您都会遇到竞争条件open(2)
系统调用将阻塞在 FIFO / 管道上,直到另一端打开。因此,例如,如果进程pid1
打开 FIFO 进行读取,open(2)
将阻塞直到pid2
打开 FIFO 进行写入。换句话说,打开一个没有活动 reader 或写入器的 FIFO 会隐式同步两个进程并保证进程不会从还没有写入器的管道中读取,或者写入器不会写入管道还没有 reader。但是正如我们将看到的,这将是有问题的。
到底发生了什么
当你这样做时:
cat xaa > p1
cat xab > p1
cat xac > p1
事情真的很慢,因为人类很慢。输入第一行后,cat
打开 p1
进行写入。另一个 cat
在打开它进行阅读时被阻止(或者可能还没有,但让我们假设它是)。一旦两个 cat
进程都打开 p1
- 一个用于写入,另一个用于读取 - 数据开始流动。
然后,在您甚至有机会输入下一个命令行 (cat xab >p1
) 之前,整个文件都流过管道,大家都很高兴 - cat
reader 进程发现管道上的文件结束,调用 close(2)
,cat
编写器完成写入文件,并关闭 p1
。 cat
reader 移动到下一个文件(又是 p1
),打开它,然后阻塞,因为还没有活动的作者打开 fifo。
然后,你,慢人,输入下一个命令行,这会导致另一个 cat
writer 进程打开 FIFO,这会解锁另一个正在等待打开以供读取的 cat
,一切都会再次发生。然后再次用于第三个命令行。
当您将所有内容都放在 shell 的一行中时,事情发生得太快了。
让我们区分 3 个 cat
调用。将其命名为 cat1
、cat2
和 cat3
:
cat1 xaa > p1; cat2 xab > p1; cat3 xac > p1
shell按顺序执行每条命令,等待上一条命令完成后再转到下一条。
然而,可能只是 cat1
将所有内容写入 p1
并退出,shell 移动到 cat2
,这会打开 FIFO然后又开始写p1
的内容,cat
reader本来就没来得及读完cat1
写的内容,现在突然cat
reader "thinks" 它仍在从第一个文件(第一个 p1
)读取,但在某个时候它开始读取 cat2
开始推入的数据管道(就像在第一个 p1
中一样)。如果cat2
更快,它无法知道第一个"copy"的数据已经结束,并且在cat
reader完成读取之前打开FIFO cat1
写道。
是的,很微妙,但这正是正在发生的事情。
然后,当然,输入最终也结束了,cat
reader会认为第一个p1
已经完成了,然后移到下一个[=19= 】,打开等待下一位作者打开。但永远不会有下一个作家!它永远阻塞,整个管道永远停滞不前。
如何修复
其他答案中的解决方案解决了问题。您在评论中提到这对您来说可能还不够,因为您无法控制新作者何时以及如何打开和使用管道。
所以我建议改为:
- 创建一个持久写入进程,它只维护 FIFO 为写入而打开,即使它永远不会真正写入。这只是为了确保没有 window 没有作者处于活动状态并且 reader 尝试读取的时间。为此,只需
cat
在后台向p1
标准输入:cat >p1 &
。完成后,终止后台作业。 - 只在reader进程中打开管道一次。这可以通过
cat p1 | tee >(sha1sum ...)
或使用其他答案 (tee >(...) <p1
) 中提出的方法来完成。毕竟,无论您的系统多么复杂,打开一次 FIFO 就足够了; FIFO 本质上总是以先进先出的方式为您提供数据。
保留后台 cat
编写器 运行 只要您知道有新文件到达的机会/新编写器打开 FIFO 并使用它。不要忘记在知道输入结束时终止后台作业。