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


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.


Да, в нашем проекте у кого-то была такая же идея, и она прекрасно работает даже под 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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.