读取所有输入后,如何关闭 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 或类似的东西,但感觉我在这里错过了一些基本的东西。
答案1
重要的是要认识到,以读模式(而不是从中读取)打开 fifo 块会阻塞,直到其他进程也以写模式打开它(反之亦然),并且一旦发生这种情况,就会实例化管道。
然后,更多进程可以通过在该管道处于活动状态时打开 fifo 来挂接到该管道。
一旦任何进程都没有打开该管道,该管道就会被销毁,之后我们回到第一个方,一旦 fifo 再次打开以进行读写,就可以实例化另一个管道。
在:
[0] [1] cat todo.pipe | [2] while read line && echo hej $line; do :; done \ > output.pipe & # The below 3 lines is all that's changed [3] cat todo.pipe | [4] while read line && echo hej $line; do :; done \ > output.pipe & [5] echo "adam\n bertil\n carl" > todo.pipe & [6] cat < output.pipe
主 shell 进程将同时生成 4 个进程,每个进程独立且并行地生活,第一个运行第一个管道,第二个运行第二个管道,第三个运行echo
,第四个运行cat
(打开后output.pipe
)。
管道进程还将生成一个额外的进程来运行,cat todo.pipe
而原始进程将同时进行循环while
。
因此,您将有 6 个(如果算上等待最后一个的主 shell 进程,则为 7 个cat
)大部分同时启动。我在上面用[1]
..标记了它们[6]
。
如何调度它们取决于系统的进程调度程序。运行外部命令(例如cat
需要时间),外壳本身执行的操作可能会首先发生。
所有 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 将继续read()
对该 fd 执行 a 操作,该 fd 将挂起,因为管道中还没有任何内容。
5 可能是此时将要安排的一个进程。这正在运行echo
shell 的内置命令,因此它只会执行 awrite("adam...)
并终止,这涉及到将其 fd 关闭output.pipe
。
然后 1read()
现在可以继续进行,cat
读取大块并将整个小输出吞掉read()
并完成,这涉及到将其 fd 关闭到管道的写入端。
如果到那时3还没有打开fifo,那么管道就会被销毁,当3最终打开fifo时,它将挂起,直到其他东西以写入模式打开fifo并实例化一个新的不相关的管道,该管道不会发生在这里。
如果不是因为它们output.pipe
先被打开,你也可能会遇到与 fifo 相同的问题。
现在,即使你做到了:
{
cat | while...done &
cat | while...done
} < todo.pipe > output.pipe &
echo ... > todo.pipe &
cat < output.pipe
wheretodo.pipe
只打开一次用于读取,因此两者cat
共享 fd (与 相同output.pipe
),避免此类问题,这可能不会很有用。
执行cat
第一个操作的read()
会吞噬 的全部echo
输出,不为另一个执行任何操作。即使您将其替换echo
为输出大于读取缓冲区的内容,cat
从而使两个 s 有机会cat
分别抓取一些片段,每个片段最终都会以看似随机的方式被切割。
如果您删除 scat |
以让read
s 直接从管道中读取,那么情况会更糟,因为read
内置函数一次读取一个字节,因此您最终会发现两个相互竞争的read
s 依次读取一个字节。
这种方法起作用的唯一方法是使用cat
并确保流程todo.pipe
足够慢,以便在输入下一个任务之前第一个任务已经被其中一个读取cat
,一次输入一个待办事项通过一个write()
系统调用,任务不大于 4KiB,也不大于cat
的读取缓冲区大小。
更好的办法是让一个进程读取管道并将任务分派给工作人员,例如使用 GNUxargs -P
或 GNU之类的东西parallel
。