【问题标题】:Data in linux FIFO seems lostlinux FIFO中的数据似乎丢失了
【发布时间】:2010-04-26 08:45:15
【问题描述】:

我有一个 bash 脚本,它想要并行执行一些工作,我通过将每个作业放在一个在后台运行的子 shell 中来做到这一点。虽然同时运行的作业数量应该在一定范围内,但我首先将一些行放入 FIFO 中,然后在分叉子 shell 之前,父脚本需要从该 FIFO 中读取一行。只有在它得到一行之后,它才能分叉子shell。到目前为止,一切正常。但是当我试图从子shell中的FIFO读取一行时,似乎只有一个子shell可以得到一行,即使FIFO中显然有更多行。所以我想知道为什么即使 FIFO 中有更多行,其他子外壳也无法读取一行。
我的测试代码如下所示:


#!/bin/sh

fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path

process_num=5
#put $process_num lines in the FIFO

for ((i=0; i<${process_num}; i++)); do
    echo "$i"
done >&6

delay_some(){
    local index="$1"
    echo "This is what u can see. $index \n"
    sleep 20;
}

#In each iteration, try to read 2 lines from FIFO, one from this shell,
#the other from the subshell
for i in 1 2
do
    date >>/tmp/fy_date
#If a line can be read from FIFO, run a subshell in bk, otherwise, block.
    read -u6
    echo " $$ Read --- $REPLY  --- from 6 \n" >> /tmp/fy_date
    {
        delay_some $i
#Try to read a line from FIFO, __ only one subshell succeeds the following line. __
        read -u6
        echo " $$ This is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
    } &
done


输出文件 /tmp/fy_date 的内容为:


Mon Apr 26 16:02:18 CST 2010
 32561 Read --- 0  --- from 6 \n
Mon Apr 26 16:02:18 CST 2010
 32561 Read --- 1  --- from 6 \n
 32561 This is in child # 1, read --- 2 --- from 6 \n

在那里,我期待这样的一行:


 32561 This is in child # 2, read --- 3 --- from 6 \n

但它永远不会出现,并且子 #2 进程在那里被阻止,直到我发出:
回显某些东西 > /tmp/fy_u_test2.fifo

【问题讨论】:

    标签: linux bash shell fifo


    【解决方案1】:

    您对 fifo 的写入是否可能存在一些缓冲?如果您有可用的 unbuffer,您可以尝试在 echo 前面加上它吗?我真的不知道它怎么会在这里发生,但症状很合适,所以值得一试。

    【讨论】:

    • 我猜这是问题所在。
    【解决方案2】:

    请记住,POSIX 系统上的 FIFO 本质上是一个命名管道。为了在管道上移动数据,一侧需要读取器,另一侧需要写入器,当其中一个关闭时,另一侧将失去用处。

    换句话说,在其他读取器退出后,您不能在 fifo 上 cat,因为 FIFO 的内容将消失。

    您可能希望了解如何使用普通文件(并使用文件锁定来确保您同步访问该普通文件),或者使用其中包含多个文件的目录,甚至使用共享内存或类似的东西到那个(也许不是在shell脚本中,虽然)。这完全取决于您的最终目标是什么,实际上,实现它的最佳方式是什么。

    【讨论】:

    • 听起来很合理,但它无法解释我的原始帖子中的症状,因为当一个子外壳被分叉时,它会获取 FIFO fd 的副本以供读取和写入。因此,只要有一个正在运行的子shell,FIFO 就同时具有读取器和写入器。
    • 我还有一个问题,关于 FIFO 中的数据何时被内核丢弃。当 FIFO 的最后一个读取器关闭其读取端时,内核是否会丢弃其中的所有剩余数据?谢谢。
    • IIRC,第一次关闭,就这样。 POSIX 未定义打开一个 FIFO 以进行读/写(请参阅linux.die.net/man/7/fifo),并且该页面说在 Linux 下为读写打开一个 FIFO 时要小心,否则可能会发生死锁。如果我正在使用 FIFO 做你正在做的事情,我将使用单个进程进行写入,使用单个进程进行读取,并使用另一种机制在进程之间进行通信来发送/接收数据。我没有看到允许多个进程安全地共享一个 FIFO(或就此而言的普通管道)。
    • 您可能还想查看 GNU shmm,它声称能够为 shell 脚本使用共享内存。或者您可以查看 POSIX 消息队列,如果您安装了一整套手册页,请参阅“man mq_overview”。
    【解决方案3】:

    似乎与'read -u6' shell 调用有关。如果我关闭了 shell 的 STDIN,当发出“read -u6”时,它会尝试从 fd 6 中读取 128 个字节。但如果 STDIN 保持不变,当发出“read -u6”时,它会读取一个字节一个直到遇到'\n'。我从“strace”中发现了这个奇怪的动作,在第一种情况下,“read -u6”调用导致了以下系统调用:

    read(6, "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n"..., 128) = 50
    

    在后一种情况下,“读取 -u6”调用导致以下系统调用:

    30371 16:27:15 read(6, "0", 1)          = 1
    30371 16:27:15 read(6, "\n", 1)         = 1
    

    测试代码如下:

    
    #!/bin/bash
    
    fifo_path="/tmp/fy_u_test2.fifo"
    mkfifo $fifo_path
    #open fifo for r/w at fd 6
    exec 6<> $fifo_path
    
    #comment or decomment the following line makes difference
    exec 0>&-
    
    process_num=20
    #put $process_num lines in the FIFO
    for ((i=0;i<${process_num};i++));do
        echo "$i"
    done >&6
    
    delay_some(){
        local index="$1"
        echo "This is what u can see. $index \n"
        sleep 10;
    }
    
    #In each iteration, try to read 2 lines from FIFO, one from this shell,
    #the other from the subshell
    for i in 1 2 3
    do
        date >>/tmp/fy_date
    #If a line can be read from FIFO, run a subshell in bk, otherwise, block.
        read -u6
        echo " $$ Read --- $REPLY  --- from 6 \n" >> /tmp/fy_date
        {
            delay_some $i
    #Try to read a line from FIFO
    #   read -u6
            echo " $$ This is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
            echo " $$ Again this is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
            echo "$i xx" >&6
    #       echo xx >&6
        } &
    done
    
    #sleep 13
    #wait
    #read -u6
    echo "$$ After fork, in parent, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
    

    【讨论】:

      【解决方案4】:

      当我运行它时,我得到了日志文件中的所有四行。如果将 shebang 更改为 #!/bin/bash 会发生什么?

      【讨论】:

      • 谢谢,但这没什么区别。顺便说一下,我的 bash 有以下版本信息: GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu) Copyright (C) 2004 Free Software Foundation, Inc.
      【解决方案5】:

      这可能是一个并发问题,两个子 shell 都试图同时从同一个 fifo 读取。它总是发生吗?

      您可以尝试添加flock -x 6 语句或更改两个子shell 的延迟,看看会发生什么。

      顺便说一句,我可以确认使用 bash 3.2 和内核 2.6.28 您的代码可以正常工作。

      【讨论】:

        【解决方案6】:

        我发现当父 shell 退出时,FIFO 中未读取的数据在父 shell 退出时丢失了。
        如果我有以下代码:

        
        #!/bin/sh
        
        fifo_path="/tmp/fy_u_test2.fifo"
        mkfifo $fifo_path
        #open fifo for r/w at fd 6
        exec 6<> $fifo_path
        
        process_num=9
        #put $process_num lines in the FIFO
        
        for ((i=0;i<${process_num};i++));do
        echo "$i"
        done >&6
        
        for i in 1 2 3;
        do
         read -u6
        done
        

        此代码结束后,命令 'cat /tmp/fy_u_test2.fifo' 什么也没有。
        但是如果我有以下代码。

        
        #!/bin/sh
        
        fifo_path="/tmp/fy_u_test2.fifo"
        mkfifo $fifo_path
        #open fifo for r/w at fd 6
        exec 6<> $fifo_path
        
        process_num=9
        #put $process_num lines in the FIFO
        
        for ((i=0;i<${process_num};i++));do
        echo "$i"
        done >&6
        
        for i in 1 2 3;
        do
         read -u6
        done
        #__ notice this line __
        sleep 60
        

        发出此代码运行后,在其休眠 60 秒期间,命令“cat /tmp/fy_u_test2.fifo”提供以下输出:

        $猫/tmp/fy_u_test2.fifo 3 4 5 6 7 8

        【讨论】:

          【解决方案7】:

          由于此处其他答案中解释的原因,除非您可以同时从管道读取和写入,否则您不需要管道。

          因此,建议使用另一种 IPC 方法或重组您对 fifos 的使用,以便异步进程填满管道,而主进程创建工作进程(或相反)。

          这是一种使用简单文件作为队列来获取所需内容的方法:

          #!/usr/bin/env bash
          
          stack=/tmp/stack
          > "$stack"
          
          # Create an initial 5 spots on the stack
          for i in {1..5}; do
              echo >> "$stack"
          done
          
          for i in {1..10}; do
              # Wait for a spot on the stack.
              until read; do sleep 1; done
          
              {
                  echo "Starting process #$i"
                  sleep $((5 + $i)) # Do something productive
                  echo "Ending process #$i"
          
                  # We're done, free our spot on the stack.
                  echo >> "$stack"
              } &
          done < "$stack"
          

          旁注:此方法不适合无限工作,因为它会为它调用的每个进程在堆栈文件中添加一个字节,这意味着堆栈文件增长缓慢。

          【讨论】:

            猜你喜欢
            • 2018-07-13
            • 1970-01-01
            • 1970-01-01
            • 2016-09-28
            • 1970-01-01
            • 2017-09-08
            • 1970-01-01
            • 1970-01-01
            • 2013-06-08
            相关资源
            最近更新 更多