Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 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

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

Ответ 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