Допустим, у меня есть цикл в Bash:
for foo in `some-command`
do
do-something $foo
done
do-something
привязан к процессору, и у меня есть красивый 4-ядерный процессор. Я хотел бы иметь возможность запускать до 4 do-something
-х одновременно.
Наивный подход выглядит так:
for foo in `some-command`
do
do-something $foo &
done
Это будет работать все do-something
с на один раз, но есть пара минусов, в основном , что делать-то , возможно , также имеет некоторый существенный ввод / вывод , которые исполняющее все сразу может замедлить немного. Другая проблема заключается в том, что этот блок кода возвращается немедленно, поэтому нет возможности выполнять другую работу, когда все do-something
s завершены.
Как бы вы написали этот цикл, чтобы X всегда do-something
выполнялись одновременно?
Ответы:
В зависимости от того, что вы хотите сделать, 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.
источник
find [...] -print0
иxargs -0
.cpus=$(getconf _NPROCESSORS_ONLN)
--max-procs=0
чтобы получить как можно больше процессов?--max-procs=0
больше похоже на попытку вопрошающего (запустить столько процессов, сколько аргументов).С GNU Parallel http://www.gnu.org/software/parallel/ вы можете написать:
GNU Parallel также поддерживает выполнение заданий на удаленных компьютерах. Это будет работать по одному на ядро ЦП на удаленных компьютерах, даже если у них разное количество ядер:
Более сложный пример: здесь мы перечисляем файлы, на которых мы хотим запустить my_script. Файлы имеют расширение (может быть .jpeg). Мы хотим, чтобы вывод my_script располагался рядом с файлами в basename.out (например, foo.jpeg -> foo.out). Мы хотим запустить my_script один раз для каждого ядра компьютера, и мы хотим запустить его также на локальном компьютере. Для удаленных компьютеров мы хотим, чтобы файл был обработан и перенесен на данный компьютер. Когда my_script завершится, мы хотим, чтобы foo.out был передан обратно, а затем мы хотим, чтобы foo.jpeg и foo.out были удалены с удаленного компьютера:
cat list_of_files | \ parallel --trc {.}.out -S server1,server2,: \ "my_script {} > {.}.out"
GNU Parallel гарантирует, что вывод каждого задания не смешивается, поэтому вы можете использовать вывод как ввод для другой программы:
Дополнительные примеры смотрите в видео: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
источник
find
команды для генерации списка файлов, потому что это не только предотвращает проблему, когда внутри имени файла есть пробел,for i in ...; do
но и find может также сделать то, сfind -name \*.extension1 -or -name \*.extension2
чем GNU parallel {.} Может очень хорошо справиться.cat
, конечно .источник
Вот альтернативное решение, которое можно вставить в .bashrc и использовать для повседневного использования одного лайнера:
function pwait() { while [ $(jobs -p | wc -l) -ge $1 ]; do sleep 1 done }
Чтобы использовать его, все, что нужно сделать, это поставить
&
после заданий и вызова pwait, параметр дает количество параллельных процессов:for i in *; do do_something $i & pwait 10 done
Было бы лучше использовать
wait
вместо занятого ожидания выводаjobs -p
, но, похоже, не существует очевидного решения - дождаться завершения любого из заданных заданий вместо их всех.источник
Вместо простого 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
источник
Если вы знакомы с этой
make
командой, в большинстве случаев вы можете выразить список команд, которые хотите запустить, в виде файла makefile. Например, если вам нужно запустить $ SOME_COMMAND для файлов * .input, каждый из которых создает * .output, вы можете использовать make-файла потом просто беги
для параллельного выполнения не более ЧИСЛА команд.
источник
Хотя сделать это правильно,
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
.источник
функция для 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 }
с помощью:
источник
make -j
является умным, но без объяснения причин и с этим куском кода Awk только для записи, я воздерживаюсь от голосования за.В проекте, над которым я работаю, используется команда ожидания для управления процессами параллельной оболочки (на самом деле ksh). Чтобы решить ваши проблемы с вводом-выводом, в современной ОС, возможно, параллельное выполнение действительно повысит эффективность. Если все процессы читают одни и те же блоки на диске, только первый процесс должен будет воздействовать на физическое оборудование. Другие процессы часто могут получить блок из дискового кеша ОС в памяти. Очевидно, что чтение из памяти на несколько порядков быстрее, чем чтение с диска. Кроме того, это преимущество не требует изменения кода.
источник
Это может быть достаточно для большинства целей, но не оптимально.
#!/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
источник
Вот как мне удалось решить эту проблему в сценарии bash:
#! /bin/bash MAX_JOBS=32 FILE_LIST=($(cat ${1})) 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
источник
В самом деле поздно на вечеринку здесь, но вот другое решение.
Многие решения не обрабатывают пробелы / специальные символы в командах, не поддерживают выполнение N заданий постоянно, используют процессор в циклах занятости или полагаются на внешние зависимости (например, GNU
parallel
).С вдохновением для обработки процесса мертвых / зомби , вот чистый раствор Баша:
function run_parallel_jobs { local concurrent_max=$1 local callback=$2 local cmds=("${@:3}") local jobs=( ) while [[ "${#cmds[@]}" -gt 0 ]] || [[ "${#jobs[@]}" -gt 0 ]]; do while [[ "${#jobs[@]}" -lt $concurrent_max ]] && [[ "${#cmds[@]}" -gt 0 ]]; do local cmd="${cmds[0]}" cmds=("${cmds[@]:1}") bash -c "$cmd" & jobs+=($!) done local job="${jobs[0]}" jobs=("${jobs[@]:1}") local state="$(ps -p $job -o state= 2>/dev/null)" if [[ "$state" == "D" ]] || [[ "$state" == "Z" ]]; then $callback $job else wait $job $callback $job $? fi done }
И пример использования:
function job_done { if [[ $# -lt 2 ]]; then echo "PID $1 died unexpectedly" else echo "PID $1 exited $2" fi } cmds=( \ "echo 1; sleep 1; exit 1" \ "echo 2; sleep 2; exit 2" \ "echo 3; sleep 3; exit 3" \ "echo 4; sleep 4; exit 4" \ "echo 5; sleep 5; exit 5" \ ) # cpus="$(getconf _NPROCESSORS_ONLN)" cpus=3 run_parallel_jobs $cpus "job_done" "${cmds[@]}"
Выход:
Для обработки вывода для каждого процесса
$$
может использоваться запись в файл, например:function job_done { cat "$1.log" } cmds=( \ "echo 1 \$\$ >\$\$.log" \ "echo 2 \$\$ >\$\$.log" \ ) run_parallel_jobs 2 "job_done" "${cmds[@]}"
Выход:
источник
Вы можете использовать простой вложенный цикл 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 равным количеству имеющихся у вас процессоров.
источник
Мое решение - всегда поддерживать заданное количество процессов, отслеживать ошибки и обрабатывать непрерывные / зомби-процессы:
function log { echo "$1" } # 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="${1}" # Number of simultaneous commands to run local commandsArg="${2}" # 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 = "список некоторых доменов в командах" для foo в
some-command
doeval `some-command for $DOMAINS` & job[$i]=$! i=$(( i + 1))
сделанный
Ndomains =
echo $DOMAINS |wc -w
для i в $ (seq 1 1 $ Ndomains) do echo "ждать $ {job [$ i]}" ждать "$ {job [$ i]}" выполнено
в этой концепции будет работать для распараллеливания. Важно то, что последняя строка eval - "&", которая помещает команды в фон.
источник