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

Вы можете. Попробуйте следовать

 #{T(com.example.Addresses).URL}

Протестировано на TomCat 7 и java6

29
задан static_rtti 8 October 2009 в 13:54
поделиться

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

19
ответ дан Ole Tange 8 October 2009 в 13:54
поделиться

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

19
ответ дан Dennis Williamson 8 October 2009 в 13:54
поделиться

Следующий скрипт показывает способ сделать это с помощью функций. Вы можете поместить функции 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
ответ дан paxdiablo 8 October 2009 в 13:54
поделиться

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

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
ответ дан Aaron McDaid 8 October 2009 в 13:54
поделиться

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

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

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

3
ответ дан Mark Rushakoff 8 October 2009 в 13:54
поделиться

Вот кратчайший путь:

waitforjobs() {
    while test $(jobs -p | wc -w) -ge "$1"; do wait -n; done
}

Вызовите эту функцию, прежде чем отменить любое новое задание:

waitforjobs 10
run_another_job &

Чтобы иметь столько фоновых заданий, сколько ядер на машине, используйте $(nproc) вместо фиксированного числа, например 10.

14
ответ дан Scarabeetle 8 October 2009 в 13:54
поделиться

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

#!/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
5
ответ дан cat 8 October 2009 в 13:54
поделиться

В 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
ответ дан Tuttle 8 October 2009 в 13:54
поделиться

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

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

3
ответ дан user3769065 8 October 2009 в 13:54
поделиться

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

0
ответ дан Steven Huwig 8 October 2009 в 13:54
поделиться

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

0
ответ дан Shay 8 October 2009 в 13:54
поделиться

Трудно обойтись без 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
0
ответ дан Tomas M 8 October 2009 в 13:54
поделиться
Другие вопросы по тегам:

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