在 Bash 中读取两个 fifo

在 Bash 中读取两个 fifo

我正在尝试从两个 fifo 中读取(从一个 fifo 中读取,如果没有从另一个中读取内容,并且如果两者都没有内容,请稍后再试一次),但它一直阻塞该进程(即使使用超时选项) )。

我关注了一些从文件中读取的其他问题(如何使用 while 循环读取两个输入文件, 和将两个文件读入 IFS while 循环)现在我有这个代码:

while true; do
    while
            IFS= read -r msg; statusA=$?
            IFS= read -u 3 -r socket; statusB=$?
            [ $statusA -eq 0 ] || [ $statusB -eq 0 ]; do
        if [ ! -z "$msg" ]; then
            echo "> $msg"
        fi
        if [ ! -z "$socket" ]; then
            echo ">> $socket"
        fi
    done <"$msg_fifo" 3<"$socket_fifo"
done

我做错了什么吗?另外,我无法使用paste/cat管道,否则它会完全阻止该过程。

答案1

这是默认情况下的设计,我不确定您可以使用bash.您当然可以使用zsh(它有 select() 系统调用模块)解决这个问题,但也许 shell 目前不是适合这项工作的语言。

这个问题很简单,就像unix中的一切一样,但实际的理解和解决需要一些更深入的思考和实践知识。

造成这种情况的原因是在文件描述符上设置了阻塞标志,这是在打开任何 VFS 对象时默认由系统内核完成的。

在unix中,进程协作是通过IO边界处的阻塞来同步的,这非常直观,并且使一切变得非常优雅和简单,并且使初级和中级应用程序员的天真简单的编程“正常工作”。

当您打开有问题的对象(文件、fifo 或其他)时,在其上设置的阻塞标志可确保在没有数据可读取时,对描述符的任何读取都会立即阻塞整个进程。仅当从另一侧将一些数据填充到对象中(如果是管道)时,此块才会被解除阻塞。

常规文件是一个“例外”,至少与管道相比,因为从 IO 子系统的角度来看,它们“从不阻塞”(即使它们实际上阻塞 - 即取消文件 fd 上的阻塞标志没有效果,因为进程阻塞发生得更深在内核的存储 fd 读取例程中)。从进程 POV 磁盘读取总是即时且零时间且不会阻塞(即使系统时钟在此类读取期间实际上向前跳跃)。

您观察到此设计有两个效果:

首先,当仅处理 shell 中的常规文件时,您永远不会真正观察到 IO 阻塞的影响(因为它们永远不会真正阻塞,正如我们上面所解释的)。

其次,一旦你在 shell 中从管道的 read() 上被阻塞,就像你在这里看到的那样,你的进程基本上会永远陷入阻塞,因为这种阻塞是“真正的”,至少在更多数据没有被填充之前是这样从管道的另一侧。在该状态下,您的进程甚至不会运行并消耗 CPU 时间,内核将其从外部阻止,直到更多数据到达,因此进程超时例程甚至无法运行(因为这需要进程消耗 CPU 时间,即运行)。

您的进程将保持阻塞状态,至少直到您向管道填充足够多的数据以供读取,然后它将短暂解除阻塞,直到所有数据再次被消耗并且进程再次被阻塞。

如果你仔细思考一下,这实际上就是 shell 中的管道工作的原因。

您是否想知道复杂的 shell 管道如何以某种方式自行适应快速或慢速的程序?这就是让它发挥作用的机制。快速生成器快速喷出输出将使管道中的下一个程序读取速度更快,而管道中缓慢的数据生成器将使管道中的任何后续程序读取/运行速度变慢 - 一切都受到阻塞数据的管道的速率限制,同步整个管道就好像通过魔法。

编辑:进一步澄清

如何摆脱困境?

没有简单的方法。据我所知,不是在 bash 中。

最简单的方法是更多地思考问题并以不同的方式重新设计它。

由于上面解释了阻塞的性质,最简单的是理解 shell 程序强加的设计约束:只有一个主输入流。

这将使 shell 程序足够健壮,能够处理文件输入(不是问题)和管道。

从多个管道(即甚至两个)读取将使您的程序自然阻塞,直到它们都有数据,因此如果您可以确保两个管道始终充满数据,那么这将起作用。不幸的是,这很少起作用:当从管道中读取数据变得交织在一起和交错时,您会遇到以随机顺序填充管道的问题 - 特别是如果读取依赖于第一个管道变空,则会阻碍整个处理。我们称这种情况为僵局。

您可以通过从有问题的文件描述符中删除阻塞标志来解决从多个管道读取的问题,但现在您遇到了 IO 调度和数据复用问题,这需要适当配备的语言来处理。

恐怕 bash 的装备还不够好,即使是这样,你现在也需要了解更多这些东西是如何工作的。

答案2

我已经看过 @etosan 和 @ilkkachu 之间的对话,并且我已经测试了您的使用建议exec fd<>fifo,现在它可以工作了。我不确定这是否涉及某种问题(正如 @etosan 所说),但至少现在可以了。

exec 3<>"$socket_fifo" # to not block the read
while true; do
    while
            IFS= read -t 0.1 -r msg; statusA=$?
            IFS= read -t 0.1 -u 3 -r socket; statusB=$?
            [ $statusA -eq 0 ] || [ $statusB -eq 0 ]; do
        if [ ! -z "$msg" ]; then
            echo "> $msg"
        fi
        if [ ! -z "$socket" ]; then
            echo ">> $socket"
        fi
    done <"$msg_fifo"
done

我还将考虑您关于在这种情况下使用 Bash 的警告。

相关内容