Linux 中的“泄漏”管道

Linux 中的“泄漏”管道

假设您有一个如下所示的管道:

$ a | b

如果b停止处理标准输入,一段时间后管道将填满,并从其a标准输出写入,将阻塞(直到再次b开始处理或死亡)。

如果我想避免这种情况,我可能会想使用更大的管道(或者更简单地说,buffer(1)),如下所示:

$ a | buffer | b

这只会为我赢得更多时间,但最终a会停止。

我想要的(对于我正在解决的一个非常具体的场景)是有一个“泄漏”管道,当它满时,会从缓冲区中删除一些数据(理想情况下,逐行)以让a继续处理(正如您可能想象的那样,在管道中流动的数据是消耗性的,即处理数据b并不比能够a在不阻塞的情况下运行更重要)。

总而言之,我希望有一个有界的、泄漏的缓冲区之类的东西:

$ a | leakybuffer | b

我可能可以很容易地用任何语言实现它,我只是想知道是否有一些“准备使用”的东西(或者类似 bash 一行的东西)我错过了。

注意:在示例中我使用的是常规管道,但问题同样适用于命名管道


虽然我给出了下面的答案,但我还决定实现leakybuffer命令,因为下面的简单解决方案有一些限制:https://github.com/CAFxX/leakybuffer

答案1

最简单的方法是通过一些设置非阻塞输出的程序进行管道传输。这是简单的 perl oneliner (您可以将其另存为泄漏缓冲区)这样做:

所以你a | b变成:

a | perl -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b

它的作用是读取输入并写入输出(与 相同cat(1)),但输出是非阻塞的 - 这意味着如果写入失败,它将返回错误并丢失数据,但该过程将继续下一行输入,因为我们方便地忽略错误。进程是一种行缓冲,如您所愿,但请参阅下面的警告。

您可以使用以下命令进行测试:

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
    while read a; do echo $a; done > output

您将获得output丢失行的文件(确切的输出取决于 shell 的速度等),如下所示:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

你会看到 shell 之后丢失行的地方12773,但也是一个异常 - Perl 没有足够的缓冲区,12774\n但 for 所以1277它只是这样写 - 所以下一个数字75610不会从行的开头开始,使它很小丑陋的。

可以通过让 perl 检测写入未完全成功的时间来改进,然后尝试刷新剩余的行,同时忽略进入的新行,但这会使 perl 脚本更加复杂,因此留作练习有兴趣的读者:)

更新(对于二进制文件): 如果您不处理换行符终止的行(如日志文件或类似文件),则需要稍微更改命令,否则 perl 将消耗大量内存(取决于换行符在输入中出现的频率):

perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }' 

它也适用于二进制文件(不消耗额外的内存)。

Update2 - 更好的文本文件输出: 避免输出缓冲区(syswrite而不是print):

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
    while read a; do echo $a; done > output

似乎为我解决了“合并线”的问题:

12766
12767
12768
16384
16385
16386

(注意:可以使用perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner 来验证哪些行输出被剪切)

相关内容