Подтвердить что ты не робот

Запуск ограниченного числа дочерних процессов параллельно в 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" работает как шарм. (Поэтому я голосовал, чтобы закрыть свой вопрос)

4b9b3361

Ответ 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

Ответ 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.

Он более гибкий, чем , является частью стандартного дистрибутива, а не как 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