Как распараллелить цикл for в bash, ограничив число процессов

У меня есть скрипт, похожий на:

NUM_PROCS=$1
NUM_ITERS=$2

for ((i=0; i<$NUM_ITERS; i++)); do
    python foo.py $i arg2 &
done

Какой самый простой способ ограничить количество параллельных процессов до NUM_PROCS? Я ищу решение, которое не требует пакетов / установок / модулей (например, GNU Parallel), если это возможно.

Когда я попробовал последний подход Чарльза Даффи, я получил следующую ошибку от bash -x:

+ python run.py args 1
+ python run.py ... 3
+ python run.py ... 4
+ python run.py ... 2
+ read -r line
+ python run.py ... 1
+ read -r line
+ python run.py ... 4
+ read -r line
+ python run.py ... 2
+ read -r line
+ python run.py ... 3
+ read -r line
+ python run.py ... 0
+ read -r line

... продолжая с другими числами от 0 до 5, пока не запустится слишком много процессов для обработки системой, и сценарий bash не будет завершен.

 Tomasz05 авг. 2016 г., 04:50
Нужно поднять до стандартов публикации.
 Tomasz05 авг. 2016 г., 06:30
Вы можете попробовать это сейчас.
 Charles Duffy04 авг. 2016 г., 22:19
... Я немного удивлен, что, когда сталкиваюсь с ответом, который явно указан "в зависимости от версии bash, достаточно новой, чтобы иметьwait -n", Бегhelp wait и ищет-n не был одним из первых шагов. Или просто работаетwait -n в командной строке и посмотреть, если он дал ошибку.
 Ole Tange04 авг. 2016 г., 21:56
Знаете ли вы, что если вам разрешено запускать собственные сценарии, вы можете выполнить личную установку GNU Parallel? Можете ли вы уточнить, рассматривается ли ваша причина избегать GNU Paralleloletange.blogspot.dk/2013/04/why-not-install-gnu-parallel.html
 Charles Duffy04 авг. 2016 г., 21:46
(размещение журнала stderr с его помощьюbash -x yourscript было бы даже лучше, чемтот, поскольку это будет показывать фактические команды, как вызванные).
 Charles Duffy21 сент. 2018 г., 16:00
Покажите, пожалуйстаименно так как вы пытаетесь применить мой ответ. Вы должны поставитьpython run.py где заглушка показываетecho "Thread $i: Processing $line",set -x журнал не показывает, что он используется таким образом.
 Tomasz04 авг. 2016 г., 23:55
Я обновил мой, если вы хотите попробовать.
 Charles Duffy05 авг. 2016 г., 04:47
@tomas, вы можете отменить удаление, чтобы ОП мог это увидеть - у них недостаточно репутации, чтобы видеть удаленные ответы.
 Charles Duffy04 авг. 2016 г., 20:14
... к сожалению, принятый ответ там (ошибочно, как отредактировано, на первом предложенном дубликате) довольно ужасен.
 Cyrus04 авг. 2016 г., 20:04
Взгляни на:GNU Parallel
 Charles Duffy06 авг. 2016 г., 22:37
Re: комментарий «последнего подхода» - последний подход вызывает только фиксированное количество подпроцессов и не может запускать больше, чем это количество процессов одновременно, если вы не делаете что-то вроде фонового кода Python,что я прямо сказал вам не делать, (Или если ваш код Python самостоятельно демонизирует какие-либо компоненты). Во всяком случае, шаблон в порядке, и я не могу отладить, как тыс помощью шаблон, если я не вижу вашу фактическую реализацию.
 Charles Duffy04 авг. 2016 г., 20:42
(Кстати,seq это не стандартизированная команда - не часть bash и не часть POSIX, поэтому нет оснований полагать, что она будет присутствовать или вести себя определенным образом в любой операционной системе. И re: case для переменных оболочки, учитывая, что они совместно используют пространство имен с переменными среды, см. Четвертый абзацpubs.opengroup.org/onlinepubs/009695399/basedefs/... для соглашений POSIX).
 chepner04 авг. 2016 г., 21:57
wait -n был введен вbash 4,3.
 Charles Duffy04 авг. 2016 г., 21:45
Re: «мне не удается», есть много причин, по которым он может потерпеть неудачу; просто публиковать код бесполезно, публиковать журнал stderr, когда он запускается, было бы гораздо полезнее, поскольку это сообщило бы нам, если вы запускаете его с оболочкой не-bash, если вы запускаете его с помощью bash, который слишком старый и т. д.

Ответы на вопрос(4)

GNU, macOS / OSX, FreeBSD и NetBSD могут делать это сxargs -P, bash версии или пакеты не требуются. Вот 4 процесса одновременно:

printf "%s\0" {1..10} | xargs -0 -I @ -P 4 python foo.py @ arg2

bash У 4.4 будет интересный новый тип расширения параметров, упрощающий ответ Чарльза Даффи.

#!/bin/bash

num_procs=$1
num_iters=$2
num_jobs="\j"  # The prompt escape for number of jobs currently running
for ((i=0; i<num_iters; i++)); do
  while (( ${[email protected]} >= num_procs )); do
    wait -n
  done
  python foo.

Знаете ли вы, что если вам разрешено писать и запускать свои собственные сценарии, то вы также можете использовать GNU Parallel? По сути это Perl-скрипт в одном файле.

От README:

= Минимальная установка =

Если вам просто нужна параллель, и у вас не установлен 'make' (возможно, система старая или Microsoft Windows):

wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem
mv parallel sem dir-in-your-$PATH/bin/
seq $2 | parallel -j$1 python foo.py {} arg2

в зависимости от версии bash, достаточно новой, чтобы иметьwait -n (ждать, пока выйдет только следующая работа, в отличие от ожиданиявсе работы):

#!/bin/bash
#      ^^^^ - NOT /bin/sh!

num_procs=$1
num_iters=$2

declare -A pids=( )

for ((i=0; i<num_iters; i++)); do
  while (( ${#pids[@]} >= num_procs )); do
    wait -n
    for pid in "${!pids[@]}"; do
      kill -0 "$pid" &>/dev/null || unset "${pids[$pid]}"
    done
  done
  python foo.py "$i" arg2 & pids["$!"]=1
done

Если работает на оболочке безwait -nможно (очень неэффективно) заменить его такой командой, какsleep 0.2, чтобы опросить каждую 1/5 секунды.

Поскольку вы на самом деле читаете ввод из файла, другой подход заключается в запуске N подпроцессов, каждый из которых обрабатывает только строки, где(linenum % N == threadnum):

num_procs=$1
infile=$2
for ((i=0; i<num_procs; i++)); do
  (
    while read -r line; do
      echo "Thread $i: processing $line"
    done < <(awk -v num_procs="$num_procs" -v i="$i" \
                 'NR % num_procs == i { print }' <"$infile")
  ) &
done
wait # wait for all $num_procs subprocesses to finish
 Tomasz04 авг. 2016 г., 21:33
Что значитwait -n?
 Charles Duffy04 авг. 2016 г., 22:51
@strathallan, гдеecho это - ибез любой& после этого, так как мы запускаем его на переднем плане (цикла, который сам на заднем плане).
 Tomasz04 авг. 2016 г., 22:04
Не должно ли быть так:${#curr_jobs[@]} >= num_procs ? Обратите внимание на=.
 Tomasz04 авг. 2016 г., 22:25
@CharlesDuffy Извините, что разочаровал вас, но у меня другой подход.
 Tomasz05 авг. 2016 г., 06:04
Решение 2 не сбалансировано. У серии по модулю могут быть узкие места.
 Charles Duffy04 авг. 2016 г., 21:39
@strathallan, если бы вы записали stderr, мы могли бы посмотреть, было ли (например) сообщение об ошибке вашегоwait не поддерживает-n, что вполне вероятно. Или любые другие сообщения об ошибках. Я настоятельно рекомендую запускать с намного меньшим максимальным количеством заданий (скажем, num_procs 2 и num_iters 4) для отладки.
 Tomasz04 авг. 2016 г., 22:19
Это не создает только случай отказаанонсирует их! @CharlesDuffy
 Tomasz04 авг. 2016 г., 22:06
Или просто=?
 Charles Duffy04 авг. 2016 г., 22:47
@tomas, я возьму случай отказа, который ничего не делает навсегда по случаю отказа, которыйсогласно ОП "Сбой системы".
 Amir Masud Zare Bidaki22 сент. 2018 г., 09:14
@CharlesDuffy Я имел в виду первое решение пока:while read -r -a curr_jobs < <(jobs -p -r) \ && (( ${#curr_jobs[@]} >= num_procs ));  который не работал для меня и раздвоил неограниченные одновременные процессы: -?
 Charles Duffy04 авг. 2016 г., 22:14
@tomas,= создает случаи сбоев: если вы каким-то образом перешли (например, из-за работ, уже запущенных до запуска цикла), вы перешли бы в бесконечность. Я согласен, что>= более уместно.
 Charles Duffy04 авг. 2016 г., 21:43
@strathallan, ... кроме этого, я не могу сказать ничего полезного - как говорит Чепнер, код в комментарии не читается. Рассматриватьgist.github.com - хорошая подсветка синтаксиса, отсутствие рекламы, контроль версий, поддержка нескольких файлов в гисте (так что вы можете иметь один файл с вашим кодом, другой - с журналом запуска этого кода сbash -x), так далее.
 chepner04 авг. 2016 г., 21:23
Код @strathallan, размещенный в комментарии, не читается.
 Charles Duffy21 сент. 2018 г., 15:49
while read цикл читает входные данные изawk, который обрабатывает ваш входной файл - извлекая только те задания, которые будут выполняться отдельным «потоком». Таким образом, при запуске этого как изначально написано, у вас былоnum_procs темы, каждая со своейwhile read цикл и копияawk извлечение другого подмножества заданий для запуска этого одного потока. Изменение цикла, как вы предлагаете, приводит к совершенно другой модели выполнения, не связанной с тем, для чего был написан этот ответ.
 strathallan04 авг. 2016 г., 22:50
Извините, если это основной вопрос, но где именно в коде (linenum% N == threadnum) я бы поместил вызов для выполнения файла python?
 Charles Duffy04 авг. 2016 г., 21:37
@tomas,wait -n ожидает только одного процесса, в отличие отвсе фоновые процессы для выхода.
 Charles Duffy04 авг. 2016 г., 22:20
@tomas, если ваша идея «раскрыть» информацию о сбое заключается не в регистрации, не в быстром отказе, а в потенциально зависании системы путем создания неоправданной нагрузки ... ну, я рад, что мы не работать вместе.
 Charles Duffy04 авг. 2016 г., 21:41
@strathallan, ... также, если вы перебираете содержимое из файла,for цикл является неправильным инструментом для использования в целом; это то чтоwhile read петли для, смmywiki.wooledge.org/BashFAQ/001, Кроме того, запустите свой код черезshellcheck.net - у него есть куча цитат ошибок.
 strathallan04 авг. 2016 г., 21:22
Я пробовал как ваше предыдущее решение, так и это. Первое решение вообще не распараллеливалось (запускался один процесс); этот запустил все num_iters одновременно, а затем вывел систему из строя.
 Charles Duffy05 авг. 2016 г., 14:20
Конечно, это правда, но мы имеем дело с достаточно большими сериями, которые, как я ожидаю, будут в основном амортизировать различия во время выполнения. Конечно, в конце будут некоторые потери (когда некоторые процессы завершены, а другие нет).
 Amir Masud Zare Bidaki21 сент. 2018 г., 10:32
этоwhile команда цикла не работает для меня. Я изменил это наwhile ((`jobs -r | wc -l | tr -d " "` >= num_procs)); и теперь это работает. какова цель использованияread командование и использование-p спор сjobs ?
 Charles Duffy22 сент. 2018 г., 16:33
@Amirmasudzarebidaki, ... тем не менее, я заменил первый ответ реализацией, которая не зависит от подстановок процессов, имеющих доступ к таблице заданий родителя - какая-то версия оболочки без этого свойства является наиболее очевидной причиной для первой реализации потерпеть поражение.
 Charles Duffy21 сент. 2018 г., 21:22
@Amirmasudzarebidaki, ... ОП здесь выполнял одно задание на строку из входного файла (хотя они не очень четко объяснили это в исходном вопросе). Таким образом, ответ был адаптирован к этому варианту использования.
 Tomasz04 авг. 2016 г., 22:42
@CharlesDuffy И в твоей логике есть недостаток. Ваше решение подвержено самому случаю отказа, который вы описали. Мой будет ждать только тогда, когда ТОЧНО будет запущено столько заданий, сколько задано предельное значение процесса, а ваше может зацикливаться до тех пор, пока количество «других заданий» не станет ИЛИ больше установленного предела. И они могут бежать вечно ...
 Charles Duffy22 сент. 2018 г., 16:28
Ааа. Преимуществоread -a читать в массив, а затем${#array[@]} чтобы проверить длину этого массива, это в отличие отwc или жеtrон встроен в саму оболочку - код в первом ответе не требует никаких внешних команд, тогда как ваш конвейер имеет несколькоmkfifo/fork/exec последовательности, необходимые для выполнения. Я должен был бы повторить неспособность говорить с этим.

Ваш ответ на вопрос