Подождите, пока будут выполнены фоновые задания bash в script
Чтобы максимизировать использование ЦП (я запускаю вещи на Debian Lenny в EC2) У меня есть простой script для запуска заданий параллельно:
#!/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 &
...
Я вполне доволен этим рабочим решением, однако я не мог понять, как писать дополнительный код, который выполняется только после завершения всех циклов.
Есть ли способ получить контроль над этим?
Ответы
Ответ 1
Для этого существует встроенная команда 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.
Ответ 2
Использование GNU Parallel сделает ваш script еще короче и, возможно, более эффективным:
parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: apache-*.log
Это запустит одно задание на ядро ЦП и продолжит его выполнение до тех пор, пока все файлы не будут обработаны.
Ваше решение будет в основном разделять задания на группы перед запуском. Здесь 32 задания в 4 группах:
![Simple scheduling]()
GNU Parallel вместо этого запускает новый процесс, когда заканчивается - сохранение активных процессоров и, следовательно, экономия времени:
![GNU Parallel scheduling]()
Чтобы узнать больше:
Ответ 3
Мне нужно было сделать это недавно, и в итоге получилось следующее решение:
while true; do
wait -n || {
code="$?"
([[ $code = "127" ]] && exit 0 || exit "$code")
break
}
done;
Вот как это работает:
wait -n
завершается, как только одно из (возможно много) фоновых заданий завершается. Он всегда оценивает значение true, и цикл продолжается до:
- Код выхода
127
: последнее фоновое задание успешно завершено. В
в этом случае мы игнорируем код выхода и выходим из под-оболочки с кодом
0.
- Не удалось выполнить какое-либо фоновое задание. Мы просто выходим из под-оболочки с этим кодом выхода.
С set -e
это гарантирует, что script закончит раньше и пройдет через код выхода любого неудачного фонового задания.
Ответ 4
Это мое грубое решение:
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
}
Идея состоит в том, чтобы использовать "задания", чтобы увидеть, сколько детей активно в фоновом режиме и ждать, пока это число не упадет (ребенок выйдет). Как только ребенок существует, можно запустить следующую задачу.
Как вы можете видеть, существует также немного дополнительной логики, чтобы избежать повторного запуска одних и тех же экспериментов/команд. Он выполняет эту работу для меня. Однако эта логика может быть либо пропущена, либо улучшена (например, отметьте отметки времени создания файла, входные параметры и т.д.).