是否可以在多个读取器之间拆分 STDIN,从而有效地成为作业队列?我想将每一行传递给一个读者。命名管道几乎可以工作,但同时读取会干扰:
阅读器.sh
#!/usr/bin/env bash
while read line
do
echo $line
done < fifo
Run Code Online (Sandbox Code Playgroud)
作家.sh
#!/usr/bin/env bash
while true
do
echo "This is a test sentance"
sleep 1
done
Run Code Online (Sandbox Code Playgroud)
执行:
mkfifo fifo
./reader.sh &
./reader.sh &
./writer.sh > fifo
Run Code Online (Sandbox Code Playgroud)
偶尔输出(特别是如果读者和作者在不同的窗口中)
This is atetsnac
Ti sats etnesats etne etsnac
isats etnes etsnac
Tisi etsnac
hi etsnac
Ti sats etn
hsi etsnac
Run Code Online (Sandbox Code Playgroud)
笔记:
不,一般情况下不可能稳健地做到这一点。写入小于 PIPE_BUF(在所有 POSIX 系统上 >=512 字节)的命名管道是原子的。问题是读取不是原子的,并且没有标准(或非标准 AFAIK)方法使它们成为原子。在对管道进行阻塞读取时,如果有 1 个或更多字节可用,它们将立即被读取,读取的实际数字作为返回值返回。
因为不能保证原子性,所以除非您有另一个并发控制机制,否则您绝不能允许多个读取器......改用消息队列之类的东西。
说了这么多,为了好玩,有可能实现令人惊讶的健壮行为。基于行的cat | while read line do; ..方法似乎有效的原因cat是因为一旦它们到达就立即从管道中抢夺行,并且正如您提到的那样,一旦开始写作,读者就准备好阅读。因为它是直接读取的,所以它碰巧在正在写入的行边界处抢占行(复数)。一般来说,虽然基于行的方法不会非常健壮,因为消息边界是不可预测的。
如果您以恒定大小的块编写和阅读,<=PIPE_BUF您会做得更好。您保证永远不会阅读超过您要求的内容,并且只要您正在写入恒定大小的块,PIPE_BUF每次写入的大小都小于大小,就没有理由小于可供读取的字节块的倍数,但是,不能保证所有可用字节都会被实际读取;无论实际有多少字节可供读取,底层读取系统调用返回的字节数少于您请求的字节数都不是错误:
成功时,返回读取的字节数(零表示文件结束),文件位置按此数字前进。如果此数字小于请求的字节数,则不是错误;例如,这可能是因为现在实际可用的字节较少(可能因为我们接近文件结尾,或者因为我们正在从管道或终端读取),或者因为 read() 被中断信号。
并且可能还有其他特殊原因——如果标准没有明确说明它是有保证的以及保证的条件,请不要假设它是。
——
阅读器.sh:
#!/bin/bash
while read -N 21 packet
do
echo [$$] $packet
done<fifo
Run Code Online (Sandbox Code Playgroud)
作家.sh
#!/bin/bash
for((i=0; i<100; i++))
do
s=`printf "%020d" $i`
echo $s
echo "wrote $s" >&2
done
Run Code Online (Sandbox Code Playgroud)
执行:
mkfifo fifo
./reader.sh &
./reader.sh &
./writer.sh > fifo
Run Code Online (Sandbox Code Playgroud)
Alv*_*rez -1
您可以将阅读器更改为
#!/usr/bin/env bash
cat fifo | while read line
do
echo $line
done
Run Code Online (Sandbox Code Playgroud)
这样,它要么读取整行,要么什么也不读取。
另一个版本的问题是,从 fifo 读取的责任是内置的read,它使用 1 个字符的缓冲区进行读取,因此如果两个进程同时运行,则同一行的不同字符可以被两个进程读取。您可以使用以下命令查看它strace:
strace bash -c 'while read line; do echo $line; done < fifo'`
Run Code Online (Sandbox Code Playgroud)
cat使用更大的缓冲区来读取,因此它最终会接收整行。测试它:
strace cat fifo | while read line; do echo $line; done
Run Code Online (Sandbox Code Playgroud)
但是,我不建议将其用作作业队列,因为它似乎没有在读者之间均匀分配读取。