2009-10-08 3 views
21

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

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

+1

Я думаю, что этот вопрос может помочь вам: http://stackoverflow.com/questions/38160/parallelize-bash-script –

+1

Итак, много запутанных ответов, но никак не сказать bash "максимум десять одновременных заданий!". Наверное, тогда нет никого. Жаль, что это действительно хорошая функция. –

ответ

15

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

+2

Удивительно - параллельная команда тоже великолепна, вам даже не нужно делать цикл. – frabcus

+0

Синтаксис ':::' устарел, хотя есть возможность включить его для обратной совместимости, которую некоторые дистрибутивы разрешают по умолчанию (несколько странно, потому что тогда примеры в руководстве не будут работать из коробки). – tripleee

+2

@ tripleee ::: поддерживается с 2010722 года и будет в обозримом будущем. Однако ваша установка может пытаться эмулировать параллель Tollef, не сообщая вам, что объясняет, почему вы считаете это странным. Удаление/etc/parallel/config должно устранить вашу проблему. –

12

Небольшой Баш скрипт может помочь вам:

# 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 является уродливым, но я не нашел способ дождаться завершения первого задания.

+0

детские процессы станут зомби. где-то происходит ожидание. – torbatamas

0

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

0

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

+1

Единственная проблема с этим - это заставить процессы умереть, а не блокировать и ждать, что является желательным поведением. – Benj

+1

Это решение опасно и трудно контролировать. Так как мои сценарии оболочки, как правило, содержат много расширений подповерхности и трубопроводов, каждая строка обычно нуждается в 4+ процессах. Когда вы устанавливаете ulimit всего процесса, это не только ограничивает количество заданий, но также ограничивает возможности, необходимые для выполнения остальной части скрипта, что приводит к непредсказуемому блокированию/сбою. – amphetamachine

3

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

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

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

16

В следующем сценарии показан способ сделать это с помощью функций. Вы можете поставить 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 
+0

Очень приятно, спасибо! –

5

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

#!/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 
+0

Что не оптимально? – naught101

+4

Вы начинаете 10 заданий, затем ждите, пока все 10 закончите, прежде чем начать еще 10 заданий. Некоторое время у вас работает только 1 работа вместо 10. Это не хорошо, если у вас медленные и быстрые задания, смешанные вместе. – cat

6

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

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 
} 
+1

Я обнаружил, что мне нужно было использовать «jobs -pr», а не просто «jobs -p», иначе он никогда не закончил последнее задание и не пропустил бы первое задание, если бы я установил ограничение на 1 задание за раз. – BenjaminBallard

0

В Linux я использую это, чтобы ограничить задания bash количеством доступных CPU (pos sired overriden, установив 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 
5

Вот самый короткий путь:

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

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

waitforjobs 10 
run_another_job & 

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

+0

Удивительный, но для версии bash> = 4 – user3769065

+0

'wait -n' не доступен для всех систем ... – willsteel

+0

Это условие гонки - если одно из заданий заканчивается, прежде чем вы дойдете до 'wait', тогда вы может быть в положении, когда вы могли бы запустить другое задание, но должны ждать, пока «wait» не поймает другое задание. –

0

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

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

Смежные вопросы