Параллельное выполнение команд с ограничением количества одновременных команд

23

Последовательный: for i in {1..1000}; do do_something $i; done- слишком медленно

Параллельно: for i in {1..1000}; do do_something $i& done- слишком большая нагрузка

Как выполнять команды параллельно, но не более, например, 20 экземпляров в минуту?

Сейчас обычно используют взломать, как for i in {1..1000}; do do_something $i& sleep 5; done, но это не очень хорошее решение.

Обновление 2 : преобразовал принятый ответ в скрипт: http://vi-server.org/vi/parallel

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Обратите внимание, что вы должны заменить 8 пробелов на 2 табуляции перед "i =", чтобы это работало.

Vi.
источник

Ответы:

15

GNU Parallel сделан для этого.

seq 1 1000 | parallel -j20 do_something

Он даже может запускать задания на удаленных компьютерах. Вот пример перекодирования MP3 в OGG с использованием server2 и локального компьютера, выполняющего 1 задание на ядро ​​ЦП:

parallel --trc {.}.ogg -j+0 -S server2,: \
     'mpg321 -w - {} | oggenc -q0 - -o {.}.ogg' ::: *.mp3

Смотрите вступительное видео для GNU Parallel здесь:

http://www.youtube.com/watch?v=OpaiGYxkSuQ

Оле Танге
источник
Не знал о «moreutils» и о том, что уже есть инструмент для работы. Смотря и сравнивая.
Ви.
1
Параметр parallelin moreutils не является GNU Parallel и довольно ограничен в своих возможностях. Команда выше не будет работать с параллелью из moreutils.
Оле Танге
1
Еще один вариант: xargs --max-procs=20.
Ви.
4

Не bash-решение, но вы должны использовать Makefile, возможно, -lчтобы он не превышал какую-то максимальную нагрузку.

NJOBS=1000

.PHONY = jobs
jobs = $(shell echo {1..$(NJOBS)})

all: $(jobs)

$(jobs):
    do_something $@

Затем, чтобы начать 20 работ одновременно

$ make -j20

или начать как можно больше заданий, не превышая нагрузку 5

$ make -j -l5
Бенджамин Банье
источник
Выглядит как нехакерское решение на данный момент.
Ви.
2
echo -e 'PHONY=jobs\njobs=$(shell echo {1..100000})\n\nall: ${jobs}\n\n${jobs}:\n\t\techo $@; sleep `echo $$RANDOM/6553 | bc -l`' | make -f - -j20Теперь это выглядит более хакерским снова.
Ви.
@vi: о боже ...
Бенджамин Банье
Преобразовал ваше решение в скрипт. Теперь его можно использовать с легкостью.
Ви.
2

размещение сценария в вопросе с форматированием:

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Обратите внимание, что перед «i =» вы должны заменить 8 пробелов на 2 табуляции.

Warren
источник
1

Одна простая идея:

Проверьте по модулю 20 и выполните команду оболочки shell перед do_something.

harrymc
источник
Он будет либо ждать завершения всех текущих задач (создавая провалы в графике количества задач), либо ждать одной конкретной задачи, которая может затянуться на более длительное время (снова создавая провалы в этом случае)
Vi.
@Vi: Ожидание оболочки - это все фоновые задачи, которые принадлежат этой оболочке.
harrymc
1

Вы можете использовать, psчтобы подсчитать, сколько процессов у вас запущено, и всякий раз, когда это падает ниже определенного порога, вы запускаете другой процесс.

Псевдокод:

i = 1
MAX_PROCESSES=20
NUM_TASKS=1000
do
  get num_processes using ps
  if num_processes < MAX_PROCESSES
    start process $i
    $i = $i + 1
  endif
  sleep 1 # add this to prevent thrashing with ps
until $i > NUM_TASKS
Пол Р
источник
1
for i in {1..1000}; do 
     (echo $i ; sleep `expr $RANDOM % 5` ) &
     while [ `jobs | wc -l` -ge 20 ] ; do 
         sleep 1 
     done
done
MSW
источник
Может быть while [ `jobs | wc -l` -ge 20]; do?
Ви.
конечно, но в моем примере мне нужно было бы njobsдважды вычислить , и производительность довольно важна для сценариев оболочки, которые запускают
спящие
Я имею в виду, ваша версия не работает, как ожидалось. Я переключаюсь sleep 1на, sleep 0.1и он начинает усреднять njobs до 40-50 вместо 20. Если существует более 20 рабочих мест, нам нужно дождаться окончания любой работы, а не просто ждать 1 секунду.
Ви.
0

Вы можете сделать это так.

threads=20
tempfifo=$PMS_HOME/$$.fifo

trap "exec 1000>&-;exec 1000<&-;exit 0" 2
mkfifo $tempfifo
exec 1000<>$tempfifo
rm -rf $tempfifo

for ((i=1; i<=$threads; i++))
do
    echo >&1000
done

for ((j=1; j<=1000; j++))
do
    read -u1000
    {
        echo $j
        echo >&1000
    } &
done

wait
echo "done!!!!!!!!!!"

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

Надеюсь, это поможет :)

ouyangyewei
источник