管道喂养异常

时间:2015-06-17 21:49:32

标签: bash pipe 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

接收管道挂起,没有生成校验和,虽然生成了输出文件,但它被截断 - 但是数量小于最终文件大小。

为什么第二种情况下的行为与第一种情况不同?

2 个答案:

答案 0 :(得分:1)

我不是积极的,但我认为涉及竞争条件。考虑将此作为一种更简单的替代方案:

tee >(sha1sum > sha1sum_new.txt) < p1 | gunzip > output_file.txt

并使用一个命令<{1}}提供p1

cat xaa xab xac > p1

这样,您只需打开p1一次,然后打开它只读一次。

答案 1 :(得分:1)

有趣的问题。正如另一个答案所提到的,你有一个竞争条件 - 我很确定。事实上,在这两种情况下你都有竞争条件,但在前者你很幸运它没有发生,因为你的文件很小,可以在你进入下一个命令行之前阅读。请允许我解释一下。

所以,先介绍一下背景:

  1. cat按顺序打开您作为参数提供的每个文件,将其打印到输出,然后关闭文件并转到下一个文件。 cat是按顺序打开每个文件还是先打开所有文件然后按顺序写入每个文件的确切细节可能会有所不同,但它与讨论无关。在这两种情况下,您都会遇到竞争条件
  2. open(2)系统调用将阻塞FIFO /管道,直到另一端打开。因此,例如,如果进程pid1打开FIFO进行读取,open(2)将阻塞,直到pid2打开FIFO进行写入。换句话说,打开没有活动读取器或写入器的FIFO会隐式同步这两个进程,并保证进程不会从没有编写器的管道读取,或者编写器不会写入没有读取器的管道。但正如我们将要看到的,这将是有问题的。
  3. 真正发生了什么

    执行此操作时:

    cat xaa > p1
    cat xab > p1
    cat xac > p1
    

    事情真的很慢,因为人类很慢。输入第一行后,cat会打开p1进行撰写。另一个cat在打开它时被阻止阅读(或者可能还没有,但让我们假设它是)。一旦cat个进程打开p1 - 一个用于写入,另一个用于读取 - 数据开始流动。

    然后,在你甚至有机会进入下一个命令行(cat xab >p1)之前,整个文件流过管道,每个人都很高兴 - cat读者进程看到了管道上的文件,调用close(2)cat编写器完成文件写入,并关闭p1cat读者移动到下一个文件(再次p1),打开它并阻止,因为还没有活动的作者打开了fifo。

    然后,你,慢人,进入下一个命令行,这会导致另一个cat编写器进程打开FIFO,解锁另一个等待打开阅读的cat,以及一切再次发生。然后再次为第三个命令行。

    当你把所有东西放在shell中的一行时,事情发生得太快了。

    让我们区分3 cat次调用。称之为cat1cat2cat3

    cat1 xaa > p1; cat2 xab > p1; cat3 xac > p1
    

    shell按顺序执行每个命令,等待上一个命令完成,然后再移动到下一个命令。

    然而,可能只是cat1完成了将所有内容写入p1并退出的情况,shell移动到cat2,这将打开FIFO并开始写入内容p1再一次,cat读者没有机会完成阅读cat1首先写的内容​​,现在突然cat读者&# 34;认为&#34;它仍然从第一个文件(第一个p1)读取,但在某些时候它开始读取cat2开始推入管道的数据(就像它在第一个{ {1}})。它无法知道第一个&#34;副本&#34;如果p1更快,则数据结束,并在cat2读者完成阅读cat写的内容之前打开FIFO。

    是的,微妙的,但它确切地发生了什么。

    然后,当然,输入最终会结束,cat1读者会认为第一个cat已完成并移至下一个p1,打开并等待为下一个作家打开它。但是永远不会有下一个作家!它永远阻止,整个管道永远停滞不前。

    如何修复

    另一个答案中的解决方案解决了这个问题。您在评论中提到它可能不够,因为您无法控制新作者何时以及如何打开和使用管道。

    所以我建议这样做:

    1. 创建一个持久的编写器进程,只保持FIFO打开以进行写入,即使它永远不会实际写入。只是为了确保没有编写器处于活动状态且读者尝试读取的时间窗口。为此,只需p1后台cat的{​​{1}}标准输入:p1。当你完成后,杀死后台工作。
    2. 在阅读器进程中只打开一次管道。这可以使用cat >p1 &或使用其他答案(cat p1 | tee >(sha1sum ...))中提出的方法来完成。毕竟,无论你的系统多么复杂,打开一次FIFO都应该足够了; FIFO本质上总是以先进先出的方式为您提供数据。
    3. 保持后台tee >(...) <p1编写器运行,只要您知道新文件到达/新编写器打开FIFO并使用它的可能性。当您知道输入已结束时,不要忘记终止后台作业。