Распараллелить скрипт Bash с максимальным количеством процессов
35147 просмотра
16 ответа
Допустим, у меня есть цикл в 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
операции завершены.
Как бы вы написали этот цикл, чтобы всегда были do-something
запущены X- ы одновременно?
Ответы (16)
52 плюса
В зависимости от того, что вы хотите сделать, 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.
Автор: Fritz G. Mehner
Размещён: 19.05.2009 07:50
35 плюса
С помощью 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 был помещен рядом с файлами в 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 гарантирует, что выходные данные из каждого задания не смешиваются, поэтому вы можете использовать выходные данные в качестве входных данных для другой программы:
some-command | parallel do-something | postprocess
Смотрите видео для получения дополнительных примеров: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Автор: Ole Tange Размещён: 10.06.2010 01:3722 плюса
maxjobs = 4 параллелизовать () { while [$ # -gt 0]; делать jobcnt = (`jobs -p`) if [$ {# jobcnt [@]} -lt $ maxjobs]; затем сделай что-нибудь $ 1 & сдвиг еще спать 1 фи сделанный Подождите } распараллелить arg1 arg2 "5 args на третье задание" arg4 ...Автор: bstark Размещён: 01.09.2008 06:00
11 плюса
Вместо простого 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
того, как вы просто проверите, что содержит переменная.
9 плюса
Вот альтернативное решение, которое можно вставить в .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
, но, похоже, не существует очевидного решения дождаться завершения любого из заданий вместо всех.
8 плюса
Может быть, попробуйте утилиту распараллеливания вместо переписывания цикла? Я большой поклонник xjobs. Я все время использую xjobs для массового копирования файлов по нашей сети, обычно при настройке нового сервера базы данных. http://www.maier-komor.de/xjobs.html
Автор: tessein Размещён: 01.09.2008 04:556 плюса
Хотя сделать это правильно, 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
.
5 плюса
Если вы знакомы с make
командой, большую часть времени вы можете выразить список команд, которые вы хотите запустить, в виде make-файла. Например, если вам нужно запустить $ SOME_COMMAND для файлов * .input, каждый из которых создает * .output, вы можете использовать make-файл
INPUT = a.input b.input OUTPUT = $ (INPUT: .input = .output) %.выход Вход $ (SOME_COMMAND) $ <$ @ все: $ (ВЫХОД)
а потом просто беги
make -j
выполнять не более NUMBER команд параллельно.
Автор: Idelic Размещён: 21.05.2009 08:333 плюса
функция для 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
Автор: ilnar
Размещён: 22.02.2012 10:14
2 плюса
Проект, над которым я работаю, использует команду wait для управления параллельными процессами оболочки (на самом деле ksh). Чтобы решить ваши проблемы с вводом-выводом в современных ОС, возможно, что параллельное выполнение фактически повысит эффективность. Если все процессы читают одни и те же блоки на диске, только первый процесс должен получить доступ к физическому оборудованию. Другие процессы часто смогут извлечь блок из дискового кэша ОС в памяти. Очевидно, что чтение из памяти на несколько порядков быстрее, чем чтение с диска. Кроме того, выгода не требует никаких изменений кодирования.
Автор: Jon Ericson Размещён: 03.09.2008 11:191 плюс
Это может быть достаточно для большинства целей, но не оптимально.
#!/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
Автор: cat
Размещён: 21.07.2011 09:05
0 плюса
Вы можете использовать простой вложенный цикл (замените соответствующие целые числа на 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 равным количеству процессоров, которые у вас есть.
Автор: Adam Zalcman Размещён: 19.11.2011 07:490 плюса
Вот как мне удалось решить эту проблему в 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
Автор: Fernando
Размещён: 13.10.2015 04:52
0 плюса
Мое решение - постоянно поддерживать заданное количество процессов, отслеживать ошибки и обрабатывать уязвимые / зомби-процессы:
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"
Автор: Orsiris de Jong
Размещён: 28.08.2016 08:30
0 плюса
Очень поздно на вечеринку здесь, но вот другое решение.
Многие решения не обрабатывают пробелы / специальные символы в командах, не поддерживают постоянную работу N заданий, не используют процессор в занятых циклах или не используют внешние зависимости (например, GNU parallel
).
С вдохновением для обработки мертвых / зомби , вот чистое решение Bash:
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[@]}"
Выход:
1
2
3
PID 56712 exited 1
4
PID 56713 exited 2
5
PID 56714 exited 3
PID 56720 exited 4
PID 56724 exited 5
Для обработки выходных данных для каждого процесса $$
можно использовать запись в файл, например:
function job_done {
cat "$1.log"
}
cmds=( \
"echo 1 \$\$ >\$\$.log" \
"echo 2 \$\$ >\$\$.log" \
)
run_parallel_jobs 2 "job_done" "${cmds[@]}"
Выход:
1 56871
2 56872
Автор: Skrat
Размещён: 01.02.2019 02:39
-1 плюса
$ DOMAINS = "список некоторых доменов в командах" для foo в some-command
do
eval `some-command for $DOMAINS` &
job[$i]=$!
i=$(( i + 1))
сделанный
Ndomains =echo $DOMAINS |wc -w
для i in $ (seq 1 1 $ Ndomains) сделайте эхо "wait for $ {job [$ i]}" wait "$ {job [$ i]}" сделано
в этой концепции будет работать для распараллеливания. важно, чтобы последняя строка в eval была '&', которая поместит команды в фон.
Автор: Jack Размещён: 16.01.2014 08:18Вопросы из категории :
- bash Как разрешить символические ссылки в сценарии оболочки
- bash Удалить все, кроме самых последних файлов X в Bash
- bash Распараллелить скрипт Bash с максимальным количеством процессов
- bash Как мне написать цикл для Bash
- bash Как обнулить числа в именах файлов в Bash?
- bash Get the source directory of a Bash script from within the script itself
- bash Как я могу отправить стандартный вывод одного процесса нескольким процессам, используя (желательно безымянные) каналы в Unix (или Windows)?
- bash Обработка ошибок в Bash
- bash Как подавить прекращение сообщения после убийства в bash?
- bash sudo echo "что-то" >> / etc / privilegedFile не работает
- bash Определить, существует ли функция в bash
- bash Проверка из сценария оболочки, если каталог содержит файлы
- bash Проблема с перенаправлением вывода Bash
- bash Область видимости переменной Bash
- bash Как удалить суффикс файла и часть пути из строки пути в Bash?
- bash Установка переменных среды в OS X
- bash bash: запускать несколько цепочек команд в фоновом режиме
- bash Как мне перебрать диапазон чисел, определенных переменными в Bash?
- bash Как проверить синтаксис скрипта Bash без его запуска?
- bash Быстрая команда Unix для отображения определенных строк в середине файла?