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

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

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

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

Следующий сценарий показывает способ сделать это с помощью функций. Вы можете поместить функции bgxupdate и bgxlimit в свой скрипт или добавить их в отдельный файл, который получен из вашего скрипта с помощью:

 . /path/to/bgx.sh 

Преимущество состоит в том, что вы можете поддерживать несколько групп процессов независимо (вы можете запускать, например, одну группу с лимитом 10 и другую полностью отдельную группу с лимитом 3).

Он использовал встроенные jobs bash для получения списка subprocessов, но поддерживал их в отдельных переменных. В цикле внизу вы можете увидеть, как вызвать функцию bgxlimit :

  • настройте пустую групповую переменную.
  • bgxgrp это на 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 

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

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

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

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

 waitforjobs 10 run_another_job & 

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

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

 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 } 

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

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

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

Например, есть очередь GNU или PBS . И для PBS вы можете посмотреть в Мауи для настройки.

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

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

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

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

  • Atomic UPDATE .. SELECT в Postgres
  • Как дождаться завершения всех streamов, используя ExecutorService?
  • Выбор лучшего списка параллелизма в Java
  • Java Fork / Join vs ExecutorService - когда использовать какой?
  • Существует ли ExecutorService, который использует текущий stream?
  • Есть ли способ для нескольких процессов совместно использовать прослушивающий сокет?
  • .NET Асинхронный stream чтения / записи
  • ОЖИДАНИЕ на sun.misc.Unsafe.park (родной метод)
  • Как разрешить декларацию с двойной проверкой блокировки в Java?
  • Что такое мьютекс и семафор в Java? В чем основное отличие?
  • Как продемонстрировать условия гонки вокруг ценностей, которые не опубликованы должным образом?
  • Давайте будем гением компьютера.