Запуск ограниченного числа дочерних процессов параллельно в bash? - PullRequest
24 голосов
/ 06 июля 2011

У меня есть большой набор файлов, для которых необходимо выполнить тяжелую обработку.Эта обработка в однопоточном режиме использует несколько сотен мегабайт ОЗУ (на компьютере, на котором запускается задание) и занимает несколько минут.Мой текущий сценарий использования - запуск задания hadoop для входных данных, но у меня была такая же проблема в других случаях ранее.

Чтобы полностью использовать доступную мощность ЦП, я хочу иметь возможность запустить несколькоэти задачи в paralell.

Однако очень простой пример сценария оболочки, подобного этому, снизит производительность системы из-за чрезмерной загрузки и перестановки:

find . -type f | while read name ; 
do 
   some_heavy_processing_command ${name} &
done

Так что то, что я хочу, по сути похоже на то, что"gmake -j4" делает.

Я знаю, что bash поддерживает команду "wait", но она ожидает только до завершения всех дочерних процессов.В прошлом я создавал сценарии, которые выполняют команду «ps», а затем выполняют grep дочернего процесса по имени (да, я знаю ... некрасиво).

Какое самое простое / самое чистое / лучшее решениеделать то, что я хочу?


Редактировать: Спасибо Фредерику: Да, действительно, это дубликат Как ограничить количество потоков / подпроцессов, используемых в функции в bash «Xargs --max-procs = 4» работает как шарм.(Так что я проголосовал, чтобы закрыть свой вопрос)

Ответы [ 7 ]

22 голосов
/ 18 января 2013

Я знаю, что опаздываю на вечеринку с этим ответом, но я подумал, что опубликую альтернативу, которая, ИМХО, сделает тело сценария чище и проще. (Очевидно, что вы можете изменить значения 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
20 голосов
/ 06 июля 2011
#! /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's 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 делает именно то, что вы хотите ...

9 голосов
/ 03 февраля 2013

С GNU Parallel становится проще:

find . -type f | parallel  some_heavy_processing_command {}

Узнать больше: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

4 голосов
/ 16 июля 2013

Мне кажется, я нашел более удобное решение, используя :

#!/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 $@

$(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.

3 голосов
/ 02 февраля 2012

Этот код работал довольно хорошо для меня.

Я заметил одну проблему, в которой сценарий не мог закончиться.Если вы столкнетесь со случаем, когда сценарий не завершится из-за того, что max_jobs превышает число элементов в массиве, сценарий никогда не завершится.

Чтобы предотвратить описанный выше сценарий, я добавил следующее правопосле объявления "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
0 голосов
/ 23 января 2015

Вот очень хорошая функция, которую я использовал для управления максимальным количеством заданий из 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
0 голосов
/ 19 ноября 2014

Другой вариант:

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
  "$@" &
}
start_job some_big_command1
start_job some_big_command2
start_job some_big_command3
start_job some_big_command4
...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...