一尘不染

在bash中使用命名管道-数据丢失问题

linux

在网上进行了一些搜索,找到了使用命名管道的简单“教程”。但是,当我对后台作业执行任何操作时,我似乎会丢失很多数据。

[[编辑:找到了一个简单得多的解决方案,请参阅回复。因此,我提出的问题现在是学术性的,以防万一可能需要工作服务器]]

在Linux 2.6.32-25-generic#45-Ubuntu SMP上使用Ubuntu 10.04(星期六)10月16日19:52:42 UTC
2010 x86_64 GNU / Linux

GNU bash版本4.1.5(1)-发行版(x86_64-pc-linux-gnu)。

我的bash函数是:

function jqs
{
  pipe=/tmp/__job_control_manager__
  trap "rm -f $pipe; exit"  EXIT SIGKILL

  if [[ ! -p "$pipe" ]]; then
      mkfifo "$pipe"
  fi

  while true
  do
    if read txt <"$pipe"
    then
      echo "$(date +'%Y'): new text is [[$txt]]"

      if [[ "$txt" == 'quit' ]]
      then
    break
      fi
    fi
  done
}

我在后台运行:

> jqs&
[1] 5336

现在我喂它:

for i in 1 2 3 4 5 6 7 8
do
  (echo aaa$i > /tmp/__job_control_manager__ && echo success$i &)
done

输出不一致。我经常没有得到所有成功的回声。我得到的新文本回声与成功回声最多,有时更少。

如果我从“提要”中删除“&”,它似乎可以工作,但是在读取输出之前,我一直处于阻塞状态。因此,我想让子流程受阻,而不是主流程受阻。

目的是编写一个简单的作业控制脚本,这样我最多可以并行运行10个作业,并将其余的作业排队等待以后的处理,但可靠地知道它们确实可以运行。

完整的职位经理如下:

function jq_manage
{
  export __gn__="$1"

  pipe=/tmp/__job_control_manager_"$__gn__"__
  trap "rm -f $pipe"    EXIT
  trap "break"      SIGKILL

  if [[ ! -p "$pipe" ]]; then
      mkfifo "$pipe"
  fi

  while true
  do
    date
    jobs
    if (($(jobs | egrep "Running.*echo '%#_Group_#%_$__gn__'" | wc -l) < $__jN__))
    then
      echo "Waiting for new job"
      if read new_job <"$pipe"
      then
    echo "new job is [[$new_job]]"

    if [[ "$new_job" == 'quit' ]]
    then
      break
    fi

    echo "In group $__gn__, starting job $new_job"
    eval "(echo '%#_Group_#%_$__gn__' > /dev/null; $new_job) &"
      fi
    else
      sleep 3
    fi
  done
}

function jq
{
  # __gn__ = first parameter to this function, the job group name (the pool within which to allocate __jN__ jobs)
  # __jN__ = second parameter to this function, the maximum of job numbers to run concurrently

  export __gn__="$1"
  shift
  export __jN__="$1"
  shift

  export __jq__=$(jobs | egrep "Running.*echo '%#_GroupQueue_#%_$__gn__'" | wc -l)
  if (($__jq__ '<' 1))
  then
    eval "(echo '%#_GroupQueue_#%_$__gn__' > /dev/null; jq_manage $__gn__) &"
  fi

  pipe=/tmp/__job_control_manager_"$__gn__"__

  echo $@ >$pipe
}

呼唤

jq <name> <max processes> <command>
jq abc 2 sleep 20

将开始一个过程。那部分工作正常。开始第二个,很好。似乎一个接一个地工作正常。但是,像上面更简单的示例一样,以循环开头10似乎会使系统丢失。

我将为解决这种明显的IPC数据丢失问题提供任何建议。

问候,阿兰。


阅读 388

收藏
2020-06-07

共1个答案

一尘不染

您的问题if如下:

while true
do
    if read txt <"$pipe"
    ....
done

发生的情况是您的作业队列服务器每次在循环中都在打开和关闭管道。这意味着某些客户端在尝试写入管道时会遇到“管道中断”错误-
也就是说,管道的读取器在编写器打开后消失了。

要解决此问题,请在服务器中更改循环,为整个循环打开一次管道:

while true
do
    if read txt
    ....
done < "$pipe"

这样,管道将打开一次并保持打开状态。

您将需要注意在循环中运行的内容,因为循环中的所有处理会将stdin附加到命名管道。您将要确保将循环中所有进程的stdin从其他位置重定向,否则它们可能会消耗管道中的数据。

编辑:现在的问题是,当最后一个客户端关闭管道时,读取时会出现EOF,您可以使用jilles方法复制文件描述符,或者可以确保自己也是客户端并保持写方打开的管道:

while true
do
    if read txt
    ....
done < "$pipe" 3> "$pipe"

这将使管道的写端在fd
3上保持打开状态。与stdin一样,此文件描述符也适用相同的警告。您将需要关闭它,以便任何子进程都不继承它。它的重要性可能不如stdin,但它会更干净。

2020-06-07