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

11

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

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 уже давно. Так ты знаешь лучше?

математический
источник
Аналогично этому вопросу: superuser.com/questions/153630/… Посмотрите, работает ли эта техника для вас.
JRobert
Было бы полезно, если вы разместили сообщения об ошибках.
Приостановлено до дальнейшего уведомления.
@JRobert да, я знал это, но на самом деле это не помогает, так как подход makefile не сработает, как я сказал! @Dennis: Хорошо, сначала я позволю себе запустить верхнюю часть, показывая мне больше, чем указанное количество процессов. Во-вторых, он не возвращается должным образом. В-третьих, я сказал, что выполнение заданий отменено, и это неправильно: я просто поместил индикатор echo "DONE"после цикла, который выполнялся до того, как активные задания не были завершены. => Это заставило меня думать, что работа не выполнена.
математика

Ответы:

15

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.

Жиль "ТАК - прекрати быть злым"
источник
8

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

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

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

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

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

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

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

for i in *
do
    do_something $i &
done
wait

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

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

Почему еще никто не упомянул 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.

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

Я попробовал некоторые из ответов. Они делают сценарий немного сложнее, чем нужно. В идеале использование parallelили xargsбыло бы предпочтительным, однако, если операции внутри цикла for сложны, может быть проблематично создать файлы большой и длинной строк для параллельной передачи. вместо этого мы могли бы использовать источник следующим образом

# Create a test file 
$ cat test.txt
task_test 1
task_test 2

# Create a shell source file 
$ cat task.sh
task_test()
{
    echo $1
}

# use the source under bash -c 
$ cat test.txt | xargs -n1 -I{} bash -c 'source task.sh; {}'
1
2

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

for i in *.myfiles; echo " do_something $i `derived_params $i` other_params
" >> commands.txt ; done

определить сделать что-то как do_something.sh

do_something(){
process $1
echo $2 
whatever $3 

}

выполнить с xargилиgnu parallel

   cat commands.txt | xargs -n1 -I{} -P8 bash -c 'source do_something.sh; {}'

Я предполагаю функциональную независимость итераций для for.

vegabondx
источник