Четыре задачи параллельно ... как мне это сделать?

23

У меня есть куча изображений PNG в каталоге. У меня есть приложение pngout, которое я запускаю для сжатия этих изображений. Это приложение вызывается скриптом, который я сделал. Проблема в том, что этот скрипт делает по одному, что-то вроде этого:

FILES=(./*.png)
for f in  "${FILES[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 $f R${f/\.\//}
done

Обработка только одного файла за раз, занимает много времени. После запуска этого приложения я вижу, что загрузка процессора составляет всего 10%. Итак, я обнаружил, что могу разделить эти файлы на 4 пакета, поместить каждый пакет в каталог и запустить 4 из четырех окон терминала, четырех процессов, поэтому у меня есть четыре экземпляра моего сценария, одновременно обрабатывающие эти изображения и работа занимает 1/4 времени.

Вторая проблема заключается в том, что я потерял время, разделяя изображения и пакеты и копируя скрипт в четыре директории, открывая 4 окна терминала, бла-бла ...

Как это сделать с одним сценарием, без необходимости что-либо делить?

Я имею в виду две вещи: во-первых, как мне из сценария bash запустить процесс в фоновом режиме? (просто добавьте & в конец?) Второе: как я могу прекратить отправку задач в фоновом режиме после отправки четвертых задач и поставить сценарий для ожидания завершения задач? Я имею в виду, просто отправка новой задачи в фоновом режиме, когда одна задача заканчивается, и всегда остается 4 параллельных задачи? если я не сделаю этого, цикл запустит миллионы задач в фоновый режим, и процессор засорится.

Spacedog
источник
См. Также Распараллеливание цикла for
Жиль "ТАК - перестань быть злым"

Ответы:

33

Если у вас есть копия, xargsкоторая поддерживает параллельное выполнение с -P, вы можете просто сделать

printf '%s\0' *.png | xargs -0 -I {} -P 4 ./pngout -s0 {} R{}

Что касается других идей, в Wooledge Bash wiki есть раздел в статье «Управление процессами», в котором описывается именно то, что вы хотите.

jw013
источник
2
Для этого случая также предусмотрены «параллельная версия gnu» и «xjobs». В основном это вопрос вкуса, который вы предпочитаете.
ноября
Не могли бы вы объяснить предложенную команду? Благодарность!
Евгений С
1
@EugeneS Не могли бы вы быть более конкретным о какой части? Printf собирает все файлы png и передает их через канал в xargs, который собирает аргументы из стандартного ввода и объединяет их в аргументы для pngoutкоманды, которую OP хотел выполнить. Ключевой параметр -P 4, который говорит Xargs использовать до 4 одновременных команд.
jw013
2
Извините за не точность. Мне было особенно интересно, почему вы использовали printfздесь функцию, а не просто обычную ls .. | grep .. *.png? Также меня интересовали xargsпараметры, которые вы использовали ( -0а -I{}). Благодарность!
Евгений С
3
@EugeneS Это для максимальной правильности и надежности. Имена файлов не являются строками и lsне могут использоваться для удобного и безопасного анализа имен файлов . Единственными безопасными символами, используемыми для разделения имен файлов, являются \0и /, поскольку любой другой символ, в том числе \n, может быть частью самого имени файла. В printfиспользует \0для имен файлов разграничить, и -0информирует xargsоб этом. -I{}Говорит xargsзаменить {}с аргументом.
jw013
8

В дополнение к уже предложенным решениям вы можете создать make-файл, который описывает, как сделать сжатый файл из несжатого, и использовать его make -j 4для параллельного запуска 4 заданий. Проблема в том, что вам нужно будет по-разному называть сжатые и несжатые файлы или хранить их в разных каталогах, иначе будет невозможно написать разумное правило make.

9000
источник
7

Если у вас установлен GNU Parallel http://www.gnu.org/software/parallel/, вы можете сделать это:

parallel ./pngout -s0 {} R{} ::: *.png

Вы можете установить GNU Parallel просто:

wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem

Посмотрите вступительные видео для GNU Parallel, чтобы узнать больше: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Оле Танге
источник
5

Чтобы ответить на два ваших вопроса:

  • да, добавление & в конце строки даст вам команду оболочки запустить фоновый процесс.
  • используя waitкоманду, вы можете попросить оболочку дождаться завершения всех процессов в фоновом режиме, прежде чем продолжить.

Вот скрипт, модифицированный так, чтобы jон использовался для отслеживания количества фоновых процессов. При NB_CONCURRENT_PROCESSESдостижении сценарий сбрасывается jдо 0 и ожидает завершения всех фоновых процессов, прежде чем возобновить его выполнение.

files=(./*.png)
nb_concurrent_processes=4
j=0
for f in "${files[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 "$f" R"${f/\.\//}" &
        ((++j == nb_concurrent_processes)) && { j=0; wait; }
done
Фредерик Дьюердт
источник
1
Это будет ожидать последнего из четырех одновременных процессов, а затем запустит набор из еще четырех. Возможно, следует создать массив из четырех идентификаторов PID, а затем дождаться этих конкретных идентификаторов PID?
Нильс
Просто чтобы объяснить мои исправления в коде: (1) В целях стиля избегайте имен всех переменных в верхнем регистре, поскольку они потенциально конфликтуют с внутренними переменными оболочки. (2) Добавлено цитирование $fи т. Д. (3) Используйте [для POSIX-совместимых скриптов, но для чистого bash [[всегда предпочтительнее. В этом случае ((больше подходит для арифметики.
jw013