读取所有输入后,如何关闭 FIFO 的所有读取器?似乎我只能关闭其中一个,这使我的程序无法完成。
这是一个有效的示例程序(测试将其放入文件中):
set -euo pipefail
rm -f todo.pipe
mkfifo todo.pipe
rm -f output.pipe
mkfifo output.pipe
cat todo.pipe | \
while read line && echo hej $line; do :; done \
> output.pipe &
echo "adam\n bertil\n carl" > todo.pipe &
cat < output.pipe
正如预期的那样,输出是:
❯ ./test.zsh
hej adam
hej bertil
hej carl
❯
但是,如果我添加另一个线程来处理这些事情todo.pipe
,事情就会永远挂起:
set -euo pipefail
rm -f todo.pipe
mkfifo todo.pipe
rm -f output.pipe
mkfifo output.pipe
cat todo.pipe | \
while read line && echo hej $line; do :; done \
> output.pipe &
# The below 3 lines is all that's changed
cat todo.pipe | \
while read line && echo hej $line; do :; done \
> output.pipe &
echo "adam\n bertil\n carl" > todo.pipe &
cat < output.pipe
现在,它的打印结果和以前一样,但它永远不会返回。为什么?我怎样才能解决这个问题?
我的怀疑是第二个“工作线程”现在获得了 EOF 或类似的东西,但感觉就像我在这里遗漏了一些基本的东西。
重要的是要意识到以读取模式打开(而不是读取)FIFO 会阻塞块,直到其他进程也以写入模式打开它(反之亦然),并且一旦发生这种情况,管道就会被实例化。
然后,更多进程可以通过在该管道处于活动状态时打开 fifo 来连接到该管道。
一旦没有从任何进程中打开的 fd,该管道就会被销毁,之后我们回到方形,只要再次打开 fifo 进行读取和写入,就可以实例化另一个管道。
在:
主 shell 进程将同时产生 4 个进程,每个进程独立且并行地过着自己的生活,第一个运行第一个管道,第二个运行第二个管道,第三个运行
echo
,第四个运行cat
(打开后output.pipe
)。管道也将产生一个额外的进程来运行
cat todo.pipe
,而原始进程将同时进行while
循环。因此,您将有 6 个(如果您算上等待最后一个的主 shell 进程,则为 7 个
cat
)大部分同时启动。我在上面用[1]
..标记了它们[6]
。如何安排它们取决于系统的进程调度程序。运行外部命令
cat
需要时间,shell 本身执行的操作可能会首先发生。所有的 2、4、5、6 都是从在 shell 中打开一个 fifo 文件开始的。2 和 4 开放
output.pipe
用于书写和6
阅读。它们很快就会相互解锁,并且将实例化管道。5 将挂起它的只写打开
todo.pipe
等待至少一个cat
进程以只读方式打开它。然后 1 和 3 将为此而竞争。运行
cat
涉及执行/bin/cat
,其中涉及擦除进程内存,从磁盘加载可执行文件,共享库,动态链接,执行动态链接,并最终运行其中的代码,其中cat
将解析其命令行并最终打开该 fifo 文件。只要 1、3 之一打开 fifo(假设这里是 1),5 就会被解锁。1 将继续对该 fd 执行 a
read()
操作,该 fd 将挂起,因为管道中还没有任何内容。5 可能是此时要安排的一个进程。那是运行 shell 的内置
echo
命令,因此它只会执行 awrite("adam...)
并终止,这涉及将其 fd 关闭为output.pipe
.然后 1
read()
现在可以继续,cat
按大块读取,并将整个小输出吞噬read()
并完成,这涉及将其 fd 关闭到管道的写入端。如果此时 3 还没有打开 fifo,则管道被销毁,当 3 最终打开 fifo 时,它将挂起,直到其他东西以写入模式打开 fifo 并实例化一个新的不相关管道发生在这里。
如果不是因为
output.pipe
首先打开的事实,那么该 fifo 也可能会遇到同样的问题。现在,即使你成功了:
Where
todo.pipe
仅打开一次以供阅读,因此两者都cat
共享 fd(对于 相同output.pipe
),避免了此类问题,这可能不会很有用。第
cat
一个read()
会吞噬整个echo
's 的输出,而不会给另一个留下任何东西。任何即使您将其替换echo
为输出大于cat
' 读取缓冲区的东西,从而使两个cat
s 都有机会各自抓住一些片段,但每个片段最终都会以看起来像随机方式的方式切割。如果您删除s
cat |
以让read
s 直接从管道中读取,则由于内置程序一次读取一个字节,情况会更糟read
,因此您最终会喜欢两个相互竞争read
的 s 依次读取一个字节。这种方法工作的唯一方法是使用
cat
并确保馈送进程的速度足够慢,以至于在下一个任务被馈送之前todo.pipe
,第一个任务已经被其中一个读取,一次馈送一个待办事项cat
通过一次write()
系统调用,任务不大于 4KiB 也不大于cat
的读取缓冲区大小。最好让一个进程读取管道并将任务分派给工作人员,例如使用 GNU
xargs -P
或 GNU之类的东西parallel
。