命名和未命名管道

好的,这是我无法解决的问题。在编写一个相当复杂的脚本时,我碰到了这个问题。设法将其简化到最低限度,但这仍然没有意义。

假设我有一个fifo

mkfifo foo.fifo

在一个终端上运行以下命令,然后在另一终端上将内容写入管道(echo "abc" > foo.fifo)似乎正常:

while true; do read LINE <foo.fifo; echo "LINE=$LINE"; done
LINE=abc

但是,对命令的更改如此之小,并且在read读取第一行后命令无法等待下一行:

cat a.fifo | while true; do read LINE; echo "LINE=$LINE"; done
LINE=abc
LINE=
LINE=
LINE=
[...] # At this keeps repeating endlessly

真正令人不安的是,它将等待第一行,但是随后它只是将一个空字符串读入$LINE,并且无法阻止。(很有趣,这是少数几次,我想阻止一个I / O操作:))

我以为,我真的很了解I / O重定向以及此类工作的原理,但是现在我很困惑。

那么,解决方案是什么,我想念的是什么?谁能解释这个现象?

更新:有关简短答案和快速解决方案的信息,请参见William的答案要获得更深入和完整的见解,您需要了解rici的说明

确实,如果我们消除了UUOC,问题中的两个命令行非常相似

while true; do read LINE <foo.fifo; echo "LINE=$LINE"; done

while true; do read LINE; echo "LINE=$LINE"; done <foo.fifo

它们的行为方式略有不同,但重要的一点是它们都不正确

第一个打开并从fifo读取,然后每次通过循环关闭fifo。第二个打开fifo,然后每次通过循环尝试读取它。

fifo是一个稍微复杂的状态机,了解各种转换非常重要。

打开fifo进行读取或写入将阻塞,直到某个进程将其打开为止。这样就可以独立地启动读者和作家。open调用将返回在同一时间。

如果fifo缓冲区中有数据,则从fifo读取成功。如果fifo缓冲区中没有数据,但至少有一个写入器保持fifo打开,则它将阻塞。如果fifo缓冲区中没有数据且没有写入器,则返回EOF。

如果fifo缓冲区中有空间,并且至少有一个打开fifo的读取器,则写入fifo成功。如果fifo缓冲区中没有空间,它将阻塞,但是至少有一个读取器将fifo打开。如果没有读取器,它会触发SIGPIPE(如果忽略该信号,则会失败并导致EPIPE失败)。

一旦关闭了fifo的两端,就将丢弃fifo缓冲区中剩余的所有数据。

现在,基于此,我们考虑第一种情况,即将fifo重定向到read我们有两个过程:

   reader                 writer
   --------------              --------------
1. OPEN blocks
2. OPEN succeeds          OPEN succeeds immediately
3. READ blocks
4.                        WRITE
5. READ succeeds     
6. CLOSE    /////////     CLOSE 

(编写者同样可以首先开始,在这种情况下,它将阻塞第1行而不是读取器。但是结果是相同的。第6行的CLOSE操作不同步。请参见下文。)

在第6行,fifo不再具有读取器或写入器,因此将刷新其缓冲区。因此,如果编写者写了两行而不是一行,那么在循环继续之前,第二行将被丢进位桶中。

让我们与第二种情况进行对比,在第二种情况下,读者是while循环,而不仅仅是阅读:

   reader                 writer
   ---------              ---------
 1. OPEN blocks

 2. OPEN succeeds          OPEN succeeds immediately
 3. READ blocks
 4.                        WRITE
 5. READ succeeds     
 6.                        CLOSE 
    --loop--
 7. READ returns EOF
 8. READ returns EOF
... and again   
42. and again              OPEN succeeds immediately
43. and again              WRITE
44. READ succeeds

在这里,阅读器将继续阅读行,直到用完为止。如果届时没有作家出现,读者将开始获得EOF。如果它忽略它们(例如while true; do read...),则将显示很多。

最后,让我们回到第一种情况,考虑一下两个进程循环时的可能性。在上面的描述中,我假设两个CLOSE操作都将在尝试执行任何OPEN操作之前成功执行。那是常见的情况,但没有任何保证。假设写者在读者设法完成CLOSE之前成功完成了CLOSE和OPEN。现在我们有了序列:

   reader                 writer
   --------------         --------------
1. OPEN blocks
2. OPEN succeeds          OPEN succeeds immediately
3. READ blocks
4.                        WRITE
5.                        CLOSE
5. READ succeeds          OPEN 
6. CLOSE                  
7.                        WRITE   !! SIGPIPE !! 

简而言之,第一次调用将跳过行,并且具有竞争条件,在这种情况下,编写器偶尔会收到虚假错误。第二次调用将读取所有写入的内容,并且写入程序将是安全的,但是读取器将连续接收EOF指示,而不是阻塞直到有可用数据为止。

那么正确的解决方案是什么?

除了竞争条件外,阅读器的最佳策略是阅读直到EOF,然后关闭并重新打开fifo。如果没有编写器,则第二次打开将被阻止。这可以通过嵌套循环来实现:

while :; do
  while read line; do
    echo "LINE=$line"
  done < fifo
done

不幸的是,尽管极其罕见,但仍可能产生SIGPIPE的竞争条件[请参见注释1]。一样,写者必须为写失败做好准备。

Linux上提供了一个更简单,更强大的解决方案,因为Linux允许打开fifos进行读写。这样的开放总是会立即成功。并且由于总有一个进程可以保持fifo的开放状态,因此读取将如预期的那样阻塞:

while read line; do
  echo "LINE=$line"
done <> fifo

(请注意,在bash中,“双向重定向”运算符<>仍然仅重定向stdin-或fd n形式n<>-因此,上述含义并不意味着“将stdin和stdout重定向到fifo”。)

笔记

  1. 比赛条件极为罕见的事实并不是忽略它的理由。墨菲定律指出,它将在最关键的时刻发生。例如,当关键文件损坏前需要正确的功能以创建备份时。但是,为了触发竞争条件,编写者进程需要安排其动作在某些非常紧迫的时间段内发生:

       reader                 writer
       --------------         --------------
       fifo is open           fifo is open
    1. READ blocks
    2.                        CLOSE
    3. READ returns EOF
    4.                        OPEN
    5. CLOSE
    6.                        WRITE   !! SIGPIPE !! 
    7. OPEN
    

    换句话说,编写者需要在阅读器收到EOF并通过关闭fifo做出响应之间的短暂间隔内执行OPEN。(这是写程序的OPEN不会被阻塞的唯一方法。)然后,它需要在读者关闭fifo的那一刻与随后的重新打开之间的(不同的)短暂间隔内进行写操作。(重新打开不会被阻止,因为现在作家已经打开了fifo。)

    就像我说的那样,这就是亿万种竞争条件中的一种,仅在最不合时宜的时刻出现,可能是在编写代码后的数年。但这并不意味着您可以忽略它。确保编写器已准备好处理SIGPIPE,然后重试因EPIPE失败的写操作。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

未命名的命名空间与全局声明

来自分类Dev

如何混合命名和未命名的字符串格式

来自分类Dev

解组未命名对象的未命名JSON数组

来自分类Dev

使用Jackson解析和未命名数组

来自分类Dev

未命名的函数参数

来自分类Dev

未命名结构和联合中的字段变量

来自分类Dev

未命名和命名命名空间解析

来自分类Dev

如何获取和更改未命名控件的属性?

来自分类Dev

C:尝试实现未命名的管道

来自分类Dev

利用未命名的数组

来自分类Dev

C ++“ ...未命名类型”

来自分类Dev

用未命名的管道编写我自己的Linux Shell

来自分类Dev

混合的已命名和未命名函数参数

来自分类Dev

未命名的命名空间,模板化函数和多个包含

来自分类Dev

访问“未命名”文件

来自分类Dev

因此,内联未命名命名空间?

来自分类Dev

向量未命名类型

来自分类Dev

命名管道和后台进程

来自分类Dev

未命名的函数参数

来自分类Dev

Stata和命名管道

来自分类Dev

'SystimaDieythynsis'和'Mihani'未命名类型

来自分类Dev

命名管道和“ GetNamedPipeHandleState”

来自分类Dev

错误“未命名类型”和“未声明”之间的区别

来自分类Dev

FIFO(命名管道)与常规管道(未命名管道)有何不同?

来自分类Dev

与未命名管道相比,使用命名管道有什么优势?

来自分类Dev

对列表中的命名函数和未命名函数套用形式

来自分类Dev

用未命名的管道传递int数组

来自分类Dev

如何动态创建多个未命名管道?

来自分类Dev

C中没有叉的未命名管道