70

为了最大限度地提高 CPU 使用率(我在 EC2 中的 Debian Lenny 上运行),我有一个简单的脚本来并行启动作业:

#!/bin/bash

for i in apache-200901*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200902*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200903*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200904*.log; do echo "Processing $i ..."; do_something_important; done &
...

我对这个可行的解决方案非常满意,但是我不知道如何编写仅在所有循环完成后才执行的进一步代码。

有没有办法控制这个?

4

4 回答 4

114

有一个bash内置命令。

wait [n ...]
      Wait for each specified process and return its termination  sta‐
      tus.   Each  n  may be a process ID or a job specification; if a
      job spec is given, all processes  in  that  job’s  pipeline  are
      waited  for.  If n is not given, all currently active child pro‐
      cesses are waited for, and the return  status  is  zero.   If  n
      specifies  a  non-existent  process or job, the return status is
      127.  Otherwise, the return status is the  exit  status  of  the
      last process or job waited for.
于 2009-07-15T13:48:30.983 回答
36

使用 GNU Parallel 将使您的脚本更短并且可能更高效:

parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: apache-*.log

这将为每个 CPU 内核运行一项作业,并继续执行此操作,直到处理完所有文件。

您的解决方案基本上会在运行之前将作业分成组。这里有 4 组 32 个工作:

简单的调度

GNU Parallel 会在完成后生成一个新进程 - 保持 CPU 处于活动状态,从而节省时间:

GNU 并行调度

了解更多:

于 2014-03-28T19:31:36.470 回答
13

我最近不得不这样做并最终得到以下解决方案:

while true; do
  wait -n || {
    code="$?"
    ([[ $code = "127" ]] && exit 0 || exit "$code")
    break
  }
done;

以下是它的工作原理:

wait -n一旦(可能有许多)后台作业之一退出,就会退出。它总是评估为真,循环继续进行,直到:

  1. 退出代码127:最后一个后台作业成功退出。在这种情况下,我们忽略退出代码并使用代码 0 退出子 shell。
  2. 任何后台作业都失败了。我们只需使用该退出代码退出子外壳。

使用set -e,这将保证脚本将提前终止并通过任何失败的后台作业的退出代码。

于 2017-05-04T07:41:18.810 回答
0

这是我的粗略解决方案:

function run_task {
        cmd=$1
        output=$2
        concurency=$3
        if [ -f ${output}.done ]; then
                # experiment already run
                echo "Command already run: $cmd. Found output $output"
                return
        fi
        count=`jobs -p | wc -l`
        echo "New active task #$count:  $cmd > $output"
        $cmd > $output && touch $output.done &
        stop=$(($count >= $concurency))
        while [ $stop -eq 1 ]; do
                echo "Waiting for $count worker threads..."
                sleep 1
                count=`jobs -p | wc -l`
                stop=$(($count > $concurency))
        done
}

这个想法是使用“作业”来查看有多少孩子在后台活动并等到这个数字下降(一个孩子退出)。一旦孩子存在,就可以开始下一个任务。

如您所见,还有一些额外的逻辑可以避免多次运行相同的实验/命令。它为我完成了这项工作。但是,这个逻辑可以被跳过或进一步改进(例如,检查文件创建时间戳、输入参数等)。

于 2015-05-18T16:28:33.347 回答