Распараллелить скрипт Bash с максимальным количеством процессов



допустим у меня есть цикл в bash:



for foo in `some-command`
do
do-something $foo
done


do-something привязан к процессору, и у меня есть хороший блестящий 4-ядерный процессор. Я хотел бы иметь возможность работать до 4 do-something ' s Сразу.



наивный подход кажется:



for foo in `some-command`
do
do-something $foo &
done


это будет работать всеdo-somethings сразу, но есть несколько недостатков, в основном, которые делают-что-то может также иметь некоторые значительные I/O, которые выполняют все сразу может немного замедлиться. Другой проблема в том, что этот блок кода возвращается немедленно, поэтому нет возможности выполнять другую работу, когда все do-somethings закончены.



как бы вы написали этот цикл, чтобы всегда было X do-somethingS работает сразу?

544   15  

15 ответов:

в зависимости от того, что вы хотите сделать, xargs также может помочь (здесь: преобразование документов с помощью pdf2ps):

cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps

документы:

--max-procs=max-procs
-P max-procs
       Run up to max-procs processes at a time; the default is 1.
       If max-procs is 0, xargs will run as many processes as  possible  at  a
       time.  Use the -n option with -P; otherwise chances are that only one
       exec will be done.

С GNU Parallel http://www.gnu.org/software/parallel/ Вы можете написать:

some-command | parallel do-something

GNU Parallel также поддерживает выполнение заданий на удаленных компьютерах. Это будет работать по одному на ядро процессора, на удаленных компьютерах, даже если они имеют разное количество ядер:

some-command | parallel -S server1,server2 do-something

более продвинутый пример: Здесь мы перечисляем файлы, на которых мы хотим запустить my_script. Файлы имеют расширение (возможно .jpeg). Мы хотим, чтобы вывод my_script был помещен рядом с файлами базовое имя.out (например, foo.jpeg - > foo.из.) Мы хотим запустить my_script один раз для каждого ядра компьютера, и мы хотим запустить его на локальном компьютере, тоже. Для удаленных компьютеров мы хотим, чтобы файл был обработан, переданный на данный компьютер. Когда my_script заканчивается, мы хотим foo.оттуда перевели обратно и мы потом хотим фу.jpeg и foo.выход удален с удаленного компьютера:

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"

GNU Parallel гарантирует, что вывод из каждого задания не смешивается, поэтому вы можете использовать вывод как вход для другой программы:

some-command | parallel do-something | postprocess

смотрите видео для получения дополнительных примеров:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

maxjobs=4
parallelize () {
        while [ $# -gt 0 ] ; do
                jobcnt=(`jobs -p`)
                if [ ${#jobcnt[@]} -lt $maxjobs ] ; then
                        do-something  &
                        shift  
                else
                        sleep 1
                fi
        done
        wait
}

parallelize arg1 arg2 "5 args to third job" arg4 ...

вместо простого bash используйте Makefile, а затем укажите количество одновременных заданий с make -jX где X-количество заданий, выполняемых одновременно.

или вы можете использовать wait ("man wait"): запуск нескольких дочерних процессов, вызов wait - он выйдет, когда дочерние процессы закончатся.

maxjobs = 10

foreach line in `cat file.txt` {
 jobsrunning = 0
 while jobsrunning < maxjobs {
  do job &
  jobsrunning += 1
 }
wait
}

job ( ){
...
}

Если вам нужно сохранить результат задания, то назначьте их результат переменной. После wait вы просто проверяете, что содержит переменная.

может быть, попробуйте распараллелить утилиту вместо перезаписи цикла? Я большой поклонник xjobs. Я использую xjobs все время для массового копирования файлов в нашей сети, как правило, при настройке нового сервера базы данных. http://www.maier-komor.de/xjobs.html

здесь альтернативное решение, которое может быть вставлено .bashrc и используется для повседневной один лайнер:

function pwait() {
    while [ $(jobs -p | wc -l) -ge  ]; do
        sleep 1
    done
}

использовать его, все, что нужно сделать, это поставить & после задания и вызова pwait параметр дает количество параллельных процессов:

for i in *; do
    do_something $i &
    pwait 10
done

было бы лучше использовать wait вместо занятого ожидания на выходе jobs -p, но, похоже, нет очевидного решения ждать, пока любое из заданных заданий будет завершено вместо всего их.

при этом в bash вероятно, невозможно, вы можете сделать полу-право довольно легко. bstark дал справедливое приближение права, но его имеет следующие недостатки:

  • разделение слов: вы не можете передать ему задания, которые используют в своих аргументах любой из следующих символов: пробелы, вкладки, новые строки, звезды, вопросительные знаки. Если вы это сделаете, все сломается, возможно, неожиданно.
  • он полагается на остальную часть вашего скрипта, чтобы не фон что угодно. Если вы это сделаете или позже добавите что-то в сценарий, который отправляется в фоновом режиме, потому что вы забыли, что вам не разрешалось использовать фоновые задания из-за его фрагмента, все будет сломано.

еще одно приближение, которое не имеет этих недостатков, заключается в следующем:

scheduleAll() {
    local job i=0 max=4 pids=()

    for job; do
        (( ++i % max == 0 )) && {
            wait "${pids[@]}"
            pids=()
        }

        bash -c "$job" & pids+=("$!")
    done

    wait "${pids[@]}"
}

обратите внимание, что это один легко адаптируется также проверить код выхода каждого задания, как он заканчивается, так что вы можете предупредить пользователя, если задание не удается или установить код выхода для scheduleAll по количеству заданий, которые провалились, или что-то в этом роде.

проблема с этим кодом как раз в том, что:

  • он планирует четыре (в данном случае) задания одновременно, а затем ждет завершения всех четырех. Некоторые из них могут быть выполнены раньше, чем другие, что заставит следующий пакет из четырех заданий ждать, пока не будет выполнен самый длинный из предыдущего пакета.

решение, которое заботится об этой последней проблеме, должно было бы использовать kill -0 для опроса ли процессы исчезли, вместо wait и запланировать следующую работу. Однако это вводит небольшую новую проблему: у вас есть условие гонки между окончанием задания и kill -0 проверка, закончилась ли она. Если задание закончилось и другой процесс в вашей системе запускается в то же время, принимая случайный PID, который оказывается тем из задания, которое только что закончилось,kill -0 не заметит, что ваша работа закончена, и все снова сломается.

идеальное решение не возможно в bash.

Если вы знакомы с make команда, большую часть времени вы можете выразить список команд, которые вы хотите запустить в качестве файла makefile. Например, если вам нужно запустить $SOME_COMMAND для файлов *.вход каждого из которых производит *.вывод, вы можете использовать makefile

INPUT  = a.input b.input
OUTPUT = $(INPUT:.input=.output)

%.output : %.input
    $(SOME_COMMAND) $< $@

all: $(OUTPUT)

а потом просто беги

make -j<NUMBER>

для параллельного выполнения не более нескольких команд.

функция для bash:

parallel ()
{
    awk "BEGIN{print \"all: ALL_TARGETS\n\"}{print \"TARGET_\"NR\":\n\t@-\"$0\"\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\n\"}" | make $@ -f - all
}

использование:

cat my_commands | parallel -j 4

проект, над которым я работаю, использует ждать команда для управления параллельными процессами оболочки (KSH на самом деле). Чтобы решить ваши проблемы с IO, на современной ОС возможно параллельное выполнение фактически повысит эффективность. Если все процессы читают одни и те же блоки на диске, только первый процесс должен будет ударить по физическому оборудованию. Другие процессы часто смогут получить блок из дискового кэша ОС в памяти. Очевидно, что чтение из памяти-это несколько на порядки быстрее, чем чтение с диска. Кроме того, преимущество не требует никаких изменений в кодировке.

Это может быть достаточно хорошо для большинства целей, но не является оптимальным.

#!/bin/bash

n=0
maxjobs=10

for i in *.m4a ; do
    # ( DO SOMETHING ) &

    # limit jobs
    if (( $(($((++n)) % $maxjobs)) == 0 )) ; then
        wait # wait until all have finished (not optimal, but most times good enough)
        echo $n wait
    fi
done

вы можете использовать простой вложенный цикл for (подставьте соответствующие целые числа для N и M ниже):

for i in {1..N}; do
  (for j in {1..M}; do do_something; done & );
done

это будет выполнять do_something N*M раз в M раундов, каждый раунд выполняет N заданий параллельно. Вы можете сделать N равным количеству процессоров, которые у вас есть.

вот как мне удалось решить эту проблему в скрипте bash:

 #! /bin/bash

 MAX_JOBS=32

 FILE_LIST=($(cat ))

 echo Length ${#FILE_LIST[@]}

 for ((INDEX=0; INDEX < ${#FILE_LIST[@]}; INDEX=$((${INDEX}+${MAX_JOBS})) ));
 do
     JOBS_RUNNING=0
     while ((JOBS_RUNNING < MAX_JOBS))
     do
         I=$((${INDEX}+${JOBS_RUNNING}))
         FILE=${FILE_LIST[${I}]}
         if [ "$FILE" != "" ];then
             echo $JOBS_RUNNING $FILE
             ./M22Checker ${FILE} &
         else
             echo $JOBS_RUNNING NULL &
         fi
         JOBS_RUNNING=$((JOBS_RUNNING+1))
     done
     wait
 done

мое решение всегда поддерживать заданное количество запущенных процессов, отслеживать ошибки и обрабатывать ubnterruptible / zombie процессы:

function log {
    echo ""
}

# Take a list of commands to run, runs them sequentially with numberOfProcesses commands simultaneously runs
# Returns the number of non zero exit codes from commands
function ParallelExec {
    local numberOfProcesses="" # Number of simultaneous commands to run
    local commandsArg="" # Semi-colon separated list of commands

    local pid
    local runningPids=0
    local counter=0
    local commandsArray
    local pidsArray
    local newPidsArray
    local retval
    local retvalAll=0
    local pidState
    local commandsArrayPid

    IFS=';' read -r -a commandsArray <<< "$commandsArg"

    log "Runnning ${#commandsArray[@]} commands in $numberOfProcesses simultaneous processes."

    while [ $counter -lt "${#commandsArray[@]}" ] || [ ${#pidsArray[@]} -gt 0 ]; do

        while [ $counter -lt "${#commandsArray[@]}" ] && [ ${#pidsArray[@]} -lt $numberOfProcesses ]; do
            log "Running command [${commandsArray[$counter]}]."
            eval "${commandsArray[$counter]}" &
            pid=$!
            pidsArray+=($pid)
            commandsArrayPid[$pid]="${commandsArray[$counter]}"
            counter=$((counter+1))
        done


        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            # Handle uninterruptible sleep state or zombies by ommiting them from running process array (How to kill that is already dead ? :)
            if kill -0 $pid > /dev/null 2>&1; then
                pidState=$(ps -p$pid -o state= 2 > /dev/null)
                if [ "$pidState" != "D" ] && [ "$pidState" != "Z" ]; then
                    newPidsArray+=($pid)
                fi
            else
                # pid is dead, get it's exit code from wait command
                wait $pid
                retval=$?
                if [ $retval -ne 0 ]; then
                    log "Command [${commandsArrayPid[$pid]}] failed with exit code [$retval]."
                    retvalAll=$((retvalAll+1))
                fi
            fi
        done
        pidsArray=("${newPidsArray[@]}")

        # Add a trivial sleep time so bash won't eat all CPU
        sleep .05
    done

    return $retvalAll
}

использование:

cmds="du -csh /var;du -csh /tmp;sleep 3;du -csh /root;sleep 10; du -csh /home"

# Execute 2 processes at a time
ParallelExec 2 "$cmds"

# Execute 4 processes at a time
ParallelExec 4 "$cmds"

$DOMAINS = " список некоторых доменов в командах" для фу в some-command делай

eval `some-command for $DOMAINS` &

    job[$i]=$!

    i=$(( i + 1))

сделал

Ndomains=echo $DOMAINS |wc -w

для i в $(seq 1 1 $Ndomains) делать Эхо " подождите ${job[$i]}" подождите " ${job[$i]}" сделано

в этой концепции будет работать для распараллеливания. важно то, что последняя строка eval - это'&' который будет помещать команды в фоны.

Comments

    Ничего не найдено.