Есть ли простой способ ограничить количество одновременных заданий в bash? Под этим я подразумеваю создание & amp; блокировать, когда в фоновом режиме выполняется более n одновременных заданий.
Я знаю, что могу реализовать это с помощью ps | трюки в стиле grep, но есть ли более простой способ?
Если у вас установлен 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 Parallel, чтобы узнать больше: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Трудно обойтись без wait -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
вы можете использовать ulimit -u см. http://ss64.com/bash/ulimit.html
Рассматривали ли вы запуск десяти длительных процессов слушателя и связь с ними по именованным каналам?
zlib
модуль не всегда доступен, особенно в поигравших в песочнице установках.
– Chinmay Kanchi
29.10.2019, 04:52
В 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's great we executed zero external commands to check!
shift
done
# wait for all currently active child processes
wait
Следующая функция (разработанная на основе ответа тангенса выше, либо скопировать в сценарий, либо в исходный код из файла):
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) Ожидает завершения существующих фоновых задач, а не опроса, повышая эффективность быстрых задач
Если вы готовы сделать это за пределами Bash, вы должны изучить систему очередей на работу.
Например, есть очередь GNU или PBS . А для PBS, вы можете посмотреть Maui для конфигурации.
Обе системы потребуют некоторой конфигурации, но вполне возможно разрешить одновременное выполнение определенного количества заданий, только начиная новые задания в очереди, когда завершенное задание завершается. Как правило, эти системы очередей заданий используются в суперкомпьютерных кластерах, где вы хотите выделить определенный объем памяти или вычислительное время для любого заданного пакетного задания; тем не менее, нет причин, по которым вы не можете использовать один из них на одном настольном компьютере без учета времени вычислений или ограничений памяти.
Предполагая, что вы хотите написать код, подобный следующему:
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
}
Небольшой сценарий bash может помочь вам:
# content of script exec-async.sh
joblist=($(jobs -p))
while (( ${#joblist[*]} >= 3 ))
do
sleep 1
joblist=($(jobs -p))
done
$* &
Если вы позвоните:
. exec-async.sh sleep 10
... четыре раза, первые три вызова вернутся немедленно, четвертый вызов будет блокироваться, пока не будет запущено менее трех заданий.
Вам нужно запустить этот скрипт внутри текущего сеанса, добавив к нему префикс .
, потому что jobs
перечисляет только задания текущего сеанса.
sleep
внутри уродливо, но я не нашел способа дождаться первой работы, которая заканчивается.
Следующий скрипт показывает способ сделать это с помощью функций. Вы можете поместить функции bgxupdate
и bgxlimit
в свой сценарий или поместить их в отдельный файл, полученный из сценария с помощью:
. /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 [ ]
n*10
секунд, поэтому четвертый процесс не запускается, пока не завершится первый (в момент времени t = 10 или 12:38:10). Вы можете видеть, что процесс 3368 исчез из списка до добавления 1560. Или, в форме временной шкалы:
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
e.preventDefault()
перед чем-либо еще или рискнуть некоторым состоянием состязания. Спасибо за просвещение.
– Wesley Murch
16.05.2020, 03:18
Вот кратчайший путь:
waitforjobs() {
while test $(jobs -p | wc -w) -ge "$1"; do wait -n; done
}
Вызовите эту функцию, прежде чем отменить любое новое задание:
waitforjobs 10
run_another_job &
Чтобы иметь столько фоновых заданий, сколько ядер на машине, используйте $(nproc)
вместо фиксированного числа, например 10.
Это может быть достаточно для большинства целей, но не оптимально.
#!/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
e.preventDefault()
, поскольку первая строка, потому что текущий блок синхронного кода будет завершенный перед любыми обратными вызовами Ajax, инициирована. Таким образом, JS будет не , прерывают в настоящее время рабочую функцию для вызова обратного вызова Ajax - или второй обработчик событий или обратный вызов тайм-аута или что бы то ни было.
– nnnnnn
16.05.2020, 03:19