Параллельные петли оболочки

14126
math

Я хочу обработать много файлов, и, поскольку у меня здесь куча ядер, я хочу сделать это параллельно:

for i in *.myfiles; do do_something $i `derived_params $i` other_params; done 

Я знаю решение Makefile, но моим командам нужны аргументы из списка глобализации оболочки. То, что я нашел, это:

> function pwait() { > while [ $(jobs -p | wc -l) -ge $1 ]; do > sleep 1 > done > } > 

Чтобы использовать его, все, что нужно сделать, это поставить и после заданий и вызова pwait, параметр дает количество параллельных процессов:

> for i in *; do > do_something $i & > pwait 10 > done 

Но это работает не очень хорошо, например, я попытался, например, с помощью цикла for, конвертирующего много файлов, но из-за которого я получил ошибку и оставил задания отмененными.

Я не могу поверить, что это еще не сделано, так как обсуждение списка рассылки zsh уже давно устарело. Так ты знаешь лучше?

10
Аналогичен этому вопросу: http://superuser.com/questions/153630/running-commands-in-parallel-with-a-limit-of-simrallelous-number-of-commands Посмотрите, работает ли этот метод для вас. JRobert 14 лет назад 0
Было бы полезно, если вы разместили сообщения об ошибках. Dennis Williamson 14 лет назад 0
@JRobert да, я знал это, но на самом деле это не помогает, так как подход makefile не сработает, как я сказал! @Dennis: Хорошо, сначала я позволю запустить верхнюю панель, показывая мне больше, чем указанное количество процессов. Во-вторых, он не возвращается должным образом. В-третьих, я сказал, что выполнение заданий отменено, и это неправильно: я просто поместил индикатор `echo" DONE "после цикла, который выполнялся до того, как активные задания не были завершены. => Это заставило меня думать, что работа не выполнена. math 14 лет назад 0

4 ответа на вопрос

14
Gilles

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

Требование к глобализации не является препятствием: существуют реализации make, которые его поддерживают. GNU make, который имеет расширение подстановочного знака, например, $(wildcard *.c)и доступ к оболочке, такой как $(shell mycommand)(посмотрите функции в руководстве по GNU make для получения дополнительной информации). Это по умолчанию makeв Linux и доступно в большинстве других систем. Вот скелет Makefile, который вы можете адаптировать к вашим потребностям:

sources = $ (подстановочный знак * .src)  все: $ (источники: .src = .tgt)  % .tgt: $ .src do_something $ <$$ (производные_параммы $ <)> $ @ 

Запустите что-то вроде make -j4параллельного выполнения четырех заданий или make -j -l3сохранения средней нагрузки около 3.

7
Ole Tange

Я не уверен, на что похожи ваши аргументы. Но с помощью GNU Parallel http: // www.gnu.org/software/parallel/ вы можете сделать это для запуска одного задания на ядро ​​процессора:

find . | parallel -j+0 'a={}; name=$; upper=$(echo "$name" | tr "[:lower:]" "[:upper:]"); echo "$name - $upper"' 

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

parallel -j+0 lame {} -o {.}.mp3 ::: *.wav 

Посмотрите вступительное видео для GNU Parallel на http://www.youtube.com/watch?v=OpaiGYxkSuQ

6
Dennis Williamson

Не подойдет ли вам команда оболочки wait?

for i in * do do_something $i & done wait 

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

нет с 1 миллионом файлов у меня будет запущено 1 миллион процессов, или я не прав? math 14 лет назад 0
@brubelsabs: Ну, он попытается сделать миллион процессов. Вы не сказали в своем вопросе, сколько файлов вам нужно обработать. Я думаю, вам нужно использовать вложенные циклы `for`, чтобы ограничить это:` для файла в *; сделать для меня в ; do do_something "$ i" & done; Подождите; done` (непроверенный) Это должно делать десять раз и ждать, пока все десять из каждой группы не будут выполнены, прежде чем начинать следующие десять. Ваш цикл делает по одному, делая `&` moot. Смотрите вопрос, с которым ** JRobert ** связан для других вариантов. Поищите в Stack Overflow другие вопросы, похожие на ваши (и на этот). Dennis Williamson 14 лет назад 1
Если ОП ожидает миллион файлов, то у него возникнет проблема с `for i in *`. Ему придется передавать аргументы в цикл с помощью канала или чего-то еще. Тогда вместо внутреннего цикла вы можете запустить инкрементный счетчик и запустить `" micro- "wait" -s "` each "$ ((i% 32))" -eq '0' 13 лет назад 0
@DennisWilliamson: объединение `wait` с внутренним циклом счетчика мне помогло. Спасибо! Joel Purra 10 лет назад 0
3
zebediah49

Почему еще никто не упомянул xargs?

Предполагая, что у вас есть ровно три аргумента,

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; done | xargs -n 3 -P $PROCS do_something 

В противном случае используйте разделитель (для этого удобно использовать null):

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; echo -ne "\0"; done | xargs -0 -n 1 -P $PROCS do_something 

РЕДАКТИРОВАТЬ: для вышеупомянутого, каждый параметр должен быть разделен нулевым символом, а затем число параметров должно быть указано с помощью xargs -n.

Да, в нашем проекте у кого-то была такая же идея, и она прекрасно работает даже под Windows с MSys. math 13 лет назад 0

Похожие вопросы