UNIX 中的非阻塞管道

Non-blocking pipes in UNIX

脚本

此 bash 脚本创建、写入和读取命名管道。但是,管道正在阻塞,因此 cat 不会执行。

mkfifo pipe
echo Hello! > pipe // blocking
cat pipe           // deadlocked; unreachable

提案

将缓冲区附加到 pipe/fifo 文件,允许 Hello! 存储 直到它被 cat 读取。这可以通过系统调用、stdbuf 等来实现

类比

Go 编程语言以通道为特色,一种命名管道。然而,这些可以被缓冲,这样它们将非阻塞地存储消息(当然直到缓冲区已满)。范例

...

func main() {
  channel := make(chan *byte, 1) // make a channel, with a buffer of 1
  
  write("Hello!", channel)   // write "Hello!" to the channel
  read(channel, fmt.Println) // read from the channel, and print the result
}

pipes are blocking and must have a receiving end to be written to. I believe this is because pipes don't have any memory allocated to them and can't store information between reading and writing.

你在这里几乎是正确的。命名管道正在阻塞,需要 reader 和同时打开管道 的编写器 才能工作。实际上在管道后面有一个由内核管理的缓冲区,但整个操作对用户程序是透明的。缓冲区仅在 两个进程打开管道(一个用于写入,一个用于读取)之后创建。

所以在你的例子中,这里:

mkfifo pipe
echo Hello! > pipe

echo 程序将卡住等待 open() 管道与 O_WRONLY,因为 open 系统调用阻塞在 FIFO 上,直到其他进程打开相同的用于读取的 FIFO。 FIFO也不能以非阻塞的方式打开,如man 2 open指定:

ERRORS

[...]

       ENXIO  O_NONBLOCK | O_WRONLY is set, the named file is a FIFO,
              and no process has the FIFO open for reading.

您可以自己通过 运行 在 shell 中执行上述命令并注意到它无限期挂起。如果您随后从管道打开另一个 shell 和 cat,另一个 shell 上的 echo 将成功打开并写入管道,而 cat 将读取什么被写入其中。

不能在其他人打开 FIFO 进行读取之前成功打开 FIFO 进行写入。这是设计使然。


要为您的 Go 做一个类似的例子,那就是:

go write()
go read()

注意:这只是打个比方,本人围棋经验不多

我不确定这是否满足您的标准,但这似乎是一个有效的解决方法:

$ ( : < fifo & ); ( echo 'Hello!' > fifo & )
$ cat fifo
Hello! 

基本思路是打开一个reader(不消耗任何数据),这样echo就可以将数据写入管道。 (括号只是为了抑制作业控制输出,可以省略。)

使用可能更清楚:

$ sleep 1 < fifo &  echo 'Hello!' > fifo 
[1] 70954
$ sed 1q fifo
Hello!
[1]+  Done                    sleep 1 < fifo
$

但是存在一些竞争条件。如果 sleepecho 运行 之前终止(不太可能),那么 echo 将挂起。在后台运行echo比较安全