Bash: ограничить количество одновременных заданий?

Есть ли простой способ ограничить количество одновременных заданий в bash? Под этим я подразумеваю создание & amp; блокировать, когда в фоновом режиме выполняется более n одновременных заданий.

Я знаю, что могу реализовать это с помощью ps | трюки в стиле grep, но есть ли более простой способ?

30
задан 18.10.2019, 08:53

12 ответов

Если у вас установлен 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

20
ответ дан 18.10.2019, 09:00
  • 1
    Я имею про, и ни одно из других сообщений не работало в 2018-1 версии. – AnneTheAgile 21.04.2018, 09:41
  • 2
    Это удивительно - параллельная команда является большой также, Вы don' t даже должен сделать цикл. – frabcus 18.10.2019, 09:00

Трудно обойтись без 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
1
ответ дан 18.10.2019, 08:54
  • 1
    Заранее спасибо за Ваш большой ответ. It' s действительно полезный. – Chhorn Soro 04.12.2014, 02:12

вы можете использовать ulimit -u см. http://ss64.com/bash/ulimit.html

0
ответ дан 18.10.2019, 08:55
  • 1
    Это решение опасно и трудно управлять. Так как мои сценарии оболочки имеют тенденцию содержать большое расширение подоболочки и передачу по каналу, каждой строке обычно нужно 4 + процессы. При установке ulimit всего процесса он не только ограничивает, сколько заданий может выполниться, он также ограничивает вещи, необходимые для выполнения остальной части сценария, вызывая вещи блокировать/приводить к сбою непредсказуемым способом. – amphetamachine 18.10.2019, 08:55
  • 2
    Единственная проблема с этим - он, заставит процессы умирать, а не блокироваться и ожидать, который является желаемым поведением. – Benj 18.10.2019, 08:56
  • 3
    Я столкнулся с той же проблемой с zip-файлами. Я должен признать, что мой отказ не читал документацию прежде, чем попробовать пример кода из документов Python. Я думаю, что пример кода должен включать параметр ZIP_DEFLATED для создания его менее сбивающим с толку. – marcin_koss 29.10.2019, 04:52

Рассматривали ли вы запуск десяти длительных процессов слушателя и связь с ними по именованным каналам?

0
ответ дан 18.10.2019, 08:56
  • 1
    Поскольку 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
1
ответ дан 18.10.2019, 08:56
  • 1
    какое ужасное значение по умолчанию! Почему?! – gabe 29.10.2019, 04:52

Следующая функция (разработанная на основе ответа тангенса выше, либо скопировать в сценарий, либо в исходный код из файла):

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) Ожидает завершения существующих фоновых задач, а не опроса, повышая эффективность быстрых задач

5
ответ дан 18.10.2019, 08:57
  • 1
    это должно быть принятым ответом легко – wdetac 16.05.2020, 03:18

Если вы готовы сделать это за пределами Bash, вы должны изучить систему очередей на работу.

Например, есть очередь GNU или PBS . А для PBS, вы можете посмотреть Maui для конфигурации.

Обе системы потребуют некоторой конфигурации, но вполне возможно разрешить одновременное выполнение определенного количества заданий, только начиная новые задания в очереди, когда завершенное задание завершается. Как правило, эти системы очередей заданий используются в суперкомпьютерных кластерах, где вы хотите выделить определенный объем памяти или вычислительное время для любого заданного пакетного задания; тем не менее, нет причин, по которым вы не можете использовать один из них на одном настольном компьютере без учета времени вычислений или ограничений памяти.

4
ответ дан 18.10.2019, 08:57
  • 1
    А-ч @nnnnnn хорошо. I' ll проверяют меня дважды на следующем проекте, но that' s большой, если it' s не проблема. Спасибо за ответ. – JamesNZ 16.05.2020, 03:18

Предполагая, что вы хотите написать код, подобный следующему:

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
}
11
ответ дан 18.10.2019, 08:58
  • 1
    I' m положительный в прошлом с некоторыми в большой степени ajax управляемые условия состязания сайтов была проблема, таким образом, первая строка во всегда была e.preventDefault (). Вы 100%-й верный it' s безопасный сделать, что you' предложение ре? – JamesNZ 16.05.2020, 03:19

Небольшой сценарий 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 внутри уродливо, но я не нашел способа дождаться первой работы, которая заканчивается.

21
ответ дан 18.10.2019, 08:58

Следующий скрипт показывает способ сделать это с помощью функций. Вы можете поместить функции 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 [ ]
  • Все начинается с 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
19
ответ дан 18.10.2019, 08:59
  • 1
    Очень хороший, спасибо! – static_rtti 18.10.2019, 09:00
  • 2
    По некоторым причинам я думал, что должен был назвать 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.

15
ответ дан 18.10.2019, 09:01
  • 1
    Нет, целая функция завершится, прежде чем действие по умолчанию продолжается (или doesn' t продолжаются если you' ve отменил его). Если бы Вы знали, что никогда не хотели действие по умолчанию для конкретного события, то имело бы смысл помещать это как первую строку где it' s очевидный (например, если Вы хотели отправить данные через Ajax, а не с формой, отправляют, или в обработчике щелчков на ссылке, которая инициировала JS вместо того, чтобы делать стандартную навигацию). Иначе помещение его в условной логике прекрасно. – nnnnnn 16.05.2020, 03:18

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

#!/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
4
ответ дан 18.10.2019, 09:01
  • 1
    @JamesNZ - I' m положительный it' s безопасный. В в большой степени основанном на Ajax сайте условия состязания могут быть проблемой, если существует несколько запросов Ajax, выполненных, прежде чем ответы возвратятся, потому что there' s никакая гарантия, что ответы возвратятся в каком-то конкретном порядке - но даже тогда Вы все еще don' t потребность e.preventDefault(), поскольку первая строка, потому что текущий блок синхронного кода будет завершенный перед любыми обратными вызовами Ajax, инициирована. Таким образом, JS будет не , прерывают в настоящее время рабочую функцию для вызова обратного вызова Ajax - или второй обработчик событий или обратный вызов тайм-аута или что бы то ни было. – nnnnnn 16.05.2020, 03:19

Теги

Похожие вопросы