为了最大化 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 &
...
我对这个工作解决方案非常满意;但是,我不知道如何编写进一步的代码,仅在所有循环完成后才执行。
有办法做到这一点吗?
有一个
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.
使用 GNU Parallel 将使您的脚本更短并且可能更高效:
parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: apache-*.log
这将为每个 CPU 核心运行一项作业,并继续执行此操作,直到处理完所有文件。
您的解决方案基本上会在运行之前将作业分组。这里 32 个职位分为 4 组:
GNU Parallel 在完成后会生成一个新进程 - 保持 CPU 处于活动状态,从而节省时间:
了解更多:
我最近不得不这样做,最终得到了以下解决方案:
while true; do
wait -n || {
code="$?"
([[ $code = "127" ]] && exit 0 || exit "$code")
break
}
done;
其工作原理如下:
一旦其中一个(可能是多个)后台作业退出,wait -n
就会退出。它的计算结果始终为 true,并且循环一直持续到:
127
:最后一个后台作业成功退出。在
在这种情况下,我们忽略退出代码并使用代码退出子 shell
0.使用
set -e
,这将保证脚本提前终止并传递任何失败的后台作业的退出代码。
一个带有
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
这是我的粗略解决方案:
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
}
这个想法是使用“jobs”来查看有多少孩子在后台活跃,并等待这个数字下降(孩子退出)。一旦孩子存在,就可以开始下一个任务。
如您所见,还有一些额外的逻辑可以避免多次运行相同的实验/命令。它为我完成了这项工作。但是,这个逻辑可以被跳过或进一步改进(例如,检查文件创建时间戳、输入参数等)。
如果您只想等待所有作业并返回,请使用以下一行。
while wait -n; do : ; done; # wait until it's possible to wait for bg job
NB.
wait
一旦多个作业中的任何一个完成即可返回