Запуск ограниченного числа дочерних процессов параллельно в bash?
У меня есть большой набор файлов, для которых нужно выполнить некоторую тяжелую обработку.
Эта обработка в однопоточном режиме использует несколько сотен мегабайтов ОЗУ (на машине, используемой для запуска задания) и занимает несколько минут для запуска.
Мое текущее usecase - начать работу с hadoop на входных данных, но у меня была такая же проблема в других случаях раньше.
Чтобы полностью использовать доступную мощность процессора, я хочу иметь возможность запускать несколько этих задач в паралелле.
Однако очень простой пример оболочки script, как это, приведет к сбою производительности системы из-за чрезмерной нагрузки и замены:
find . -type f | while read name ;
do
some_heavy_processing_command ${name} &
done
Так что я хочу, по сути, похоже на то, что делает "gmake -j4".
Я знаю, что bash поддерживает команду wait, но только до тех пор, пока все дочерние процессы не будут завершены. Раньше я создавал скрипты, которые выполняют команду "ps", а затем grep файл обрабатывает по имени (да, я знаю... уродливо).
Какое самое простое/чистое/лучшее решение для выполнения того, что я хочу?
Изменить: Спасибо Фредерику: Да, действительно, это дубликат Как ограничить количество потоков/подпроцессов, используемых в функции в bash
"Xargs -max-procs = 4" работает как шарм.
(Поэтому я голосовал, чтобы закрыть свой вопрос)
Ответы
Ответ 1
#! /usr/bin/env bash
set -o monitor
# means: run background processes in a separate processes...
trap add_next_job CHLD
# execute add_next_job when we receive a child complete signal
todo_array=($(find . -type f)) # places output into an array
index=0
max_jobs=2
function add_next_job {
# if still jobs to do then add one
if [[ $index -lt ${#todo_array[*]} ]]
# apparently stackoverflow doesn't like bash syntax
# the hash in the if is not a comment - rather it bash awkward way of getting its length
then
echo adding job ${todo_array[$index]}
do_job ${todo_array[$index]} &
# replace the line above with the command you want
index=$(($index+1))
fi
}
function do_job {
echo "starting job $1"
sleep 2
}
# add initial set of jobs
while [[ $index -lt $max_jobs ]]
do
add_next_job
done
# wait for all jobs to complete
wait
echo "done"
Сказав, что Фредрик делает отличную мысль, что xargs делает именно то, что вы хотите...
Ответ 2
Я знаю, что опаздываю на вечеринку с этим ответом, но я подумал, что отправлю альтернативу, которая, IMHO, сделает тело script более чистым и простым. (Ясно, что вы можете изменить значения 2 и 5, чтобы они соответствовали вашему сценарию.)
function max2 {
while [ `jobs | wc -l` -ge 2 ]
do
sleep 5
done
}
find . -type f | while read name ;
do
max2; some_heavy_processing_command ${name} &
done
wait
Ответ 3
С GNU Parallel он становится проще:
find . -type f | parallel some_heavy_processing_command {}
Подробнее: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Ответ 4
Думаю, я нашел более удобное решение, используя make:
#!/usr/bin/make -f
THIS := $(lastword $(MAKEFILE_LIST))
TARGETS := $(shell find . -name '*.sh' -type f)
.PHONY: all $(TARGETS)
all: $(TARGETS)
$(TARGETS):
some_heavy_processing_command [email protected]
$(THIS): ; # Avoid to try to remake this makefile
Назовите его, например. 'test.mak' и добавьте права выполнения. Если вы вызываете ./test.mak
, он будет вызывать some_heavy_processing_command
один за другим. Но вы можете назвать ./test.mak -j 4
, тогда он будет запускать сразу четыре подпроцесса. Также вы можете использовать его более сложным образом: запустите его как ./test.mak -j 5 -l 1.5
, тогда он будет работать максимум 5 подпроцессов, тогда как загрузка системы будет меньше 1,5, но она ограничит количество процессов, если загрузка системы превышает 1,5.
Он более гибкий, чем xargs, make является частью стандартного дистрибутива, а не как parallel
.
Ответ 5
Этот код работал довольно хорошо для меня.
Я заметил одну проблему, в которой script не удалось завершить.
Если вы столкнулись с ситуацией, когда script не закончится из-за того, что max_jobs больше числа элементов в массиве, script никогда не выйдет.
Чтобы предотвратить описанный выше сценарий, я добавил следующее после объявления "max_jobs".
if [ $max_jobs -gt ${#todo_array[*]} ];
then
# there are more elements found in the array than max jobs, setting max jobs to #of array elements"
max_jobs=${#todo_array[*]}
fi
Ответ 6
Другая опция:
PARALLEL_MAX=...
function start_job() {
while [ $(ps --no-headers -o pid --ppid=$$ | wc -l) -gt $PARALLEL_MAX ]; do
sleep .1 # Wait for background tasks to complete.
done
"[email protected]" &
}
start_job some_big_command1
start_job some_big_command2
start_job some_big_command3
start_job some_big_command4
...
Ответ 7
Вот очень хорошая функция, которую я использовал для управления максимальным количеством заданий из bash или ksh. ПРИМЕЧАНИЕ. - 1 в pgrep вычитает подпроцесс wc -l.
function jobmax
{
typeset -i MAXJOBS=$1
sleep .1
while (( ($(pgrep -P $$ | wc -l) - 1) >= $MAXJOBS ))
do
sleep .1
done
}
nproc=5
for i in {1..100}
do
sleep 1 &
jobmax $nproc
done
wait # Wait for the rest