Bash: ограничить количество одновременных заданий?
Есть ли простой способ ограничить количество параллельных заданий в bash? Под этим я подразумеваю создание и блокировку, когда в фоновом режиме выполняется более n параллельных заданий.
Я знаю, что могу реализовать это с помощью ps | grep-style трюки, но есть ли более простой способ?
Ответы
Ответ 1
Если у вас установлен GNU Parallel http://www.gnu.org/software/parallel/, вы можете сделать это:
parallel gzip ::: *.log
который будет запускать один gzip для ядра процессора до тех пор, пока все файлы журналов не будут скопированы.
Если это часть более крупного цикла, вы можете использовать sem
вместо:
for i in *.log ; do
echo $i Do more stuff here
sem -j+0 gzip $i ";" echo done
done
sem --wait
Он будет делать то же самое, но даст вам возможность делать больше вещей для каждого файла.
Если GNU Parallel не упакован для вашего дистрибутива, вы можете установить GNU Parallel просто:
(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash
Он будет загружать, проверять подпись и выполнять личную установку, если он не может установить глобально.
Смотрите видеоролики для GNU. Параллельно узнайте больше:
https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Ответ 2
Маленький bash script может вам помочь:
# content of script exec-async.sh
joblist=($(jobs -p))
while (( ${#joblist[*]} >= 3 ))
do
sleep 1
joblist=($(jobs -p))
done
$* &
Если вы вызываете:
. exec-async.sh sleep 10
... четыре раза первые три вызова будут немедленно возвращены, четвертый вызов будет заблокирован до тех пор, пока не будет выполнено менее трех заданий.
Вам нужно запустить этот script внутри текущего сеанса, предварительно указав его .
, потому что jobs
перечисляет только задания текущего сеанса.
sleep
внутри уродливо, но я не нашел способ дождаться завершения первого задания.
Ответ 3
Следующий script показывает способ сделать это с помощью функций. Вы можете поместить функции bgxupdate
и bgxlimit
в свой script или добавить их в отдельный файл, который получен из вашего script с помощью:
. /path/to/bgx.sh
Преимущество состоит в том, что вы можете поддерживать несколько групп процессов независимо (вы можете запускать, например, одну группу с пределом 10 и другую полностью отдельную группу с лимитом 3).
Он использовал встроенный bash
jobs
, чтобы получить список подпроцессов, но поддерживает их в отдельных переменных. В цикле внизу вы можете увидеть, как вызвать функцию bgxlimit
:
- установить пустую групповую переменную.
- передайте это значение на
bgxgrp
.
- вызов
bgxlimit
с лимитом и командой, которую вы хотите запустить.
- Перенесите новую группу обратно в свою групповую переменную.
Конечно, если у вас есть только одна группа, просто используйте bgxgrp
напрямую, а не передавайте и выходите.
#!/bin/bash
# bgxupdate - update active processes in a group.
# Works by transferring each process to new group
# if it is still active.
# in: bgxgrp - current group of processes.
# out: bgxgrp - new group of processes.
# out: bgxcount - number of processes in new group.
bgxupdate() {
bgxoldgrp=${bgxgrp}
bgxgrp=""
((bgxcount = 0))
bgxjobs=" $(jobs -pr | tr '\n' ' ')"
for bgxpid in ${bgxoldgrp} ; do
echo "${bgxjobs}" | grep " ${bgxpid} " >/dev/null 2>&1
if [[ $? -eq 0 ]] ; then
bgxgrp="${bgxgrp} ${bgxpid}"
((bgxcount = bgxcount + 1))
fi
done
}
# bgxlimit - start a sub-process with a limit.
# Loops, calling bgxupdate until there is a free
# slot to run another sub-process. Then runs it
# an updates the process group.
# in: $1 - the limit on processes.
# in: $2+ - the command to run for new process.
# in: bgxgrp - the current group of processes.
# out: bgxgrp - new group of processes
bgxlimit() {
bgxmax=$1 ; shift
bgxupdate
while [[ ${bgxcount} -ge ${bgxmax} ]] ; do
sleep 1
bgxupdate
done
if [[ "$1" != "-" ]] ; then
$* &
bgxgrp="${bgxgrp} $!"
fi
}
# Test program, create group and run 6 sleeps with
# limit of 3.
group1=""
echo 0 $(date | awk '{print $4}') '[' ${group1} ']'
echo
for i in 1 2 3 4 5 6 ; do
bgxgrp=${group1} ; bgxlimit 3 sleep ${i}0 ; group1=${bgxgrp}
echo ${i} $(date | awk '{print $4}') '[' ${group1} ']'
done
# Wait until all others are finished.
echo
bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp}
while [[ ${bgxcount} -ne 0 ]] ; do
oldcount=${bgxcount}
while [[ ${oldcount} -eq ${bgxcount} ]] ; do
sleep 1
bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp}
done
echo 9 $(date | awk '{print $4}') '[' ${group1} ']'
done
Здесь пример выполнения:
0 12:38:00 [ ]
1 12:38:00 [ 3368 ]
2 12:38:00 [ 3368 5880 ]
3 12:38:00 [ 3368 5880 2524 ]
4 12:38:10 [ 5880 2524 1560 ]
5 12:38:20 [ 2524 1560 5032 ]
6 12:38:30 [ 1560 5032 5212 ]
9 12:38:50 [ 5032 5212 ]
9 12:39:10 [ 5212 ]
9 12:39:30 [ ]
- Все начинается с 12:38:00, и, как видите, первые три процесса запускаются немедленно.
- Каждый процесс засыпает за
n*10
секунды, так что четвертый процесс не запускается до тех пор, пока первые не выйдут (в момент времени t = 10 или 12:38:10). Вы можете видеть, что процесс 3368 исчез из списка до добавления 1560.
- Аналогично, пятый процесс (5032) начинается, когда второй (5880) выходит в момент времени t = 20.
- И, наконец, шестой процесс (5212) начинается, когда третий (2524) выходит в момент времени t = 30.
- Затем начинается отсчет, четвертый процесс выходит при t = 50 (начался с 10, длительность 40), пятый при t = 70 (начался с 20, длительность 50) и шестой при t = 90 (начался с 30, продолжительность 60).
Или, в форме времени:
Process: 1 2 3 4 5 6
-------- - - - - - -
12:38:00 ^ ^ ^
12:38:10 v | | ^
12:38:20 v | | ^
12:38:30 v | | ^
12:38:40 | | |
12:38:50 v | |
12:39:00 | |
12:39:10 v |
12:39:20 |
12:39:30 v
Ответ 4
Здесь самый короткий путь:
waitforjobs() {
while test $(jobs -p | wc -w) -ge "$1"; do wait -n; done
}
Вызовите эту функцию перед тем, как отменить любое новое задание:
waitforjobs 10
run_another_job &
Чтобы иметь столько фоновых заданий, сколько ядер на машине, используйте $(nproc)
вместо фиксированного числа, такого как 10.
Ответ 5
Предполагая, что вы хотите написать такой код:
for x in $(seq 1 100); do # 100 things we want to put into the background.
max_bg_procs 5 # Define the limit. See below.
your_intensive_job &
done
Где max_bg_procs
следует поместить в .bashrc
:
function max_bg_procs {
if [[ $# -eq 0 ]] ; then
echo "Usage: max_bg_procs NUM_PROCS. Will wait until the number of background (&)"
echo " bash processes (as determined by 'jobs -pr') falls below NUM_PROCS"
return
fi
local max_number=$((0 + ${1:-0}))
while true; do
local current_number=$(jobs -pr | wc -l)
if [[ $current_number -lt $max_number ]]; then
break
fi
sleep 1
done
}
Ответ 6
Это может быть достаточно хорошим для большинства целей, но не оптимальным.
#!/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
Ответ 7
Если вы хотите сделать это за пределами чистого bash, вы должны заглянуть в систему очередей заданий.
Например, очередь GNU или PBS. И для PBS вы можете посмотреть в Maui для настройки.
Обе системы потребуют некоторой конфигурации, но вполне возможно разрешить запуск определенного количества заданий сразу, только запуск новых заданий в очереди при завершении выполняемого задания. Как правило, эти системы очередей заданий будут использоваться на суперкомпьютерных кластерах, где вы хотите выделить определенный объем памяти или вычислительное время для любого заданного пакетного задания; однако нет причин, по которым вы не можете использовать один из них на одном настольном компьютере без учета времени вычисления или памяти.
Ответ 8
Следующая функция (разработанная от тангенса, отвечающая выше, либо скопируйте в script, либо источник из файла):
job_limit () {
# Test for single positive integer input
if (( $# == 1 )) && [[ $1 =~ ^[1-9][0-9]*$ ]]
then
# Check number of running jobs
joblist=($(jobs -rp))
while (( ${#joblist[*]} >= $1 ))
do
# Wait for any job to finish
command='wait '${joblist[0]}
for job in ${joblist[@]:1}
do
command+=' || wait '$job
done
eval $command
joblist=($(jobs -rp))
done
fi
}
1) Требуется только вставка одной строки для ограничения существующего цикла
while :
do
task &
job_limit `nproc`
done
2) Ожидает завершения существующих фоновых задач, а не опроса, повышения эффективности для быстрых задач
Ответ 9
В Linux я использую это, чтобы ограничить задания bash количеством доступных ЦП (возможно, переопределив настройкой CPU_NUMBER
).
[ "$CPU_NUMBER" ] || CPU_NUMBER="`nproc 2>/dev/null || echo 1`"
while [ "$1" ]; do
{
do something
with $1
in parallel
echo "[$# items left] $1 done"
} &
while true; do
# load the PIDs of all child processes to the array
joblist=(`jobs -p`)
if [ ${#joblist[*]} -ge "$CPU_NUMBER" ]; then
# when the job limit is reached, wait for *single* job to finish
wait -n
else
# stop checking when we're below the limit
break
fi
done
# it great we executed zero external commands to check!
shift
done
# wait for all currently active child processes
wait
Ответ 10
Рассматривали ли вы запуск десяти длительных процессов прослушивания и связь с ними через именованные каналы?
Ответ 11
вы можете использовать ulimit -u
см. http://ss64.com/bash/ulimit.html
Ответ 12
Трудно обойтись без ожидания -n (например, оболочка в busybox не поддерживает его). Так что здесь есть обходной путь, он не оптимален, потому что он вызывает команды "jobs" и "wc" 10 раз в секунду. Например, вы можете уменьшить количество вызовов до 1x в секунду, если не возражаете немного подождать, пока завершится каждое задание.
# $1 = maximum concurent jobs
#
limit_jobs()
{
while true; do
if [ "$(jobs -p | wc -l)" -lt "$1" ]; then break; fi
usleep 100000
done
}
# and now start some tasks:
task &
limit_jobs 2
task &
limit_jobs 2
task &
limit_jobs 2
task &
limit_jobs 2
wait