等待脚本中的 bash 后台作业完成

发布于 2024-07-27 12:02:15 字数 562 浏览 5 评论 0原文

为了最大限度地提高 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 &
...

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

有没有办法做到这一点?

To maximize CPU usage (I run things on a Debian Lenny in EC2) I have a simple script to launch jobs in parallel:

#!/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 &
...

I'm quite satisfied with this working solution; however, I couldn't figure out how to write further code to be executed only once ALL of the loops have been completed.

Is there a way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

静谧幽蓝 2024-08-03 12:02:15

有一个 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.

There's a bash builtin command for that.

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.
旧人哭 2024-08-03 12:02:15

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

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

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

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

Simple Scheduling

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

GNU 并行调度

要了解更多信息:

Using GNU Parallel will make your script even shorter and possibly more efficient:

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

This will run one job per CPU core and continue to do that until all files are processed.

Your solution will basically split the jobs into groups before running. Here 32 jobs in 4 groups:

Simple scheduling

GNU Parallel instead spawns a new process when one finishes - keeping the CPUs active and thus saving time:

GNU Parallel scheduling

To learn more:

过潦 2024-08-03 12:02:15

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

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

其工作原理如下:

一旦其中一个(可能有很多)后台作业退出,wait -n 就会退出。 它的计算结果始终为 true,并且循环一直持续到:

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

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

I had to do this recently and ended up with the following solution:

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

Here's how it works:

wait -n exits as soon as one of the (potentially many) background jobs exits. It always evaluates to true and the loop goes on until:

  1. Exit code 127: the last background job successfully exited. In
    that case, we ignore the exit code and exit the sub-shell with code
    0.
  2. Any of the background job failed. We just exit the sub-shell with that exit code.

With set -e, this will guarantee that the script will terminate early and pass through the exit code of any failed background job.

如日中天 2024-08-03 12:02:15

使用 wait $(jobs -p) 的最小示例:

  for i in {1..3}
  do
    (echo "process $i started" && sleep 5 && echo "process $i finished")&
  done  

  sleep 0.1 # For sequential output
  echo "Waiting for processes to finish" 
  wait $(jobs -p)
  echo "All processes finished"

示例输出:

process 1 started
process 2 started
process 3 started
Waiting for processes to finish
process 2 finished
process 1 finished
process 3 finished
All processes finished

A minimal example with wait $(jobs -p):

  for i in {1..3}
  do
    (echo "process $i started" && sleep 5 && echo "process $i finished")&
  done  

  sleep 0.1 # For sequential output
  echo "Waiting for processes to finish" 
  wait $(jobs -p)
  echo "All processes finished"

Exemplary output:

process 1 started
process 2 started
process 3 started
Waiting for processes to finish
process 2 finished
process 1 finished
process 3 finished
All processes finished
橘亓 2024-08-03 12:02:15

如果您只想等待所有作业并返回,请使用以下一行。

while wait -n; do : ; done; # wait until it's possible to wait for bg job

NB 一旦多个作业中的任何一个完成,wait就会返回

If you just want to wait for all the jobs and return, use the following one-liner.

while wait -n; do : ; done; # wait until it's possible to wait for bg job

N.B. wait returns as soon as any one of several jobs is complete

勿挽旧人 2024-08-03 12:02:15

这是我的粗略解决方案:

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
}

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

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

This is my crude solution:

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
}

The idea is to use "jobs" to see how many children are active in the background and wait till this number drops (a child exits). Once a child exists, the next task can be started.

As you can see, there is also a bit of extra logic to avoid running the same experiments/commands multiple times. It does the job for me.. However, this logic could be either skipped or further improved (e.g., check for file creation timestamps, input parameters, etc.).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文