Parallele Shell-Schleifen


11

Ich möchte viele Dateien verarbeiten und da ich hier eine Reihe von Kernen habe, möchte ich dies parallel tun:

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

Ich kenne eine Makefile- Lösung, aber meine Befehle benötigen die Argumente aus der Shell-Globbing-Liste. Was ich gefunden habe ist:

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

Um es zu verwenden, muss man nur setzen und nach den Jobs und einem pwait-Aufruf gibt der Parameter die Anzahl der parallelen Prozesse an:

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

Aber das funktioniert nicht sehr gut, zB habe ich es mit zB einer for-Schleife versucht, die viele Dateien konvertiert, mir aber Fehler gibt und Jobs ungeschehen macht.

Ich kann nicht glauben, dass dies noch nicht getan ist, da die Diskussion über die zsh-Mailingliste mittlerweile so alt ist. Weißt du es besser?


Ähnlich wie bei dieser Frage: superuser.com/questions/153630/… Überprüfen Sie, ob diese Technik für Sie funktioniert.
JRobert

Es wäre hilfreich, wenn Sie die Fehlermeldungen posten würden.
Bis auf weiteres angehalten.

@JRobert Ja, ich wusste das, aber das hilft nicht wirklich, da der Makefile-Ansatz nicht wie gesagt funktioniert! @ Tennis: Ok, zuerst lasse ich ein Top laufen und zeige mir mehr als die angegebene Anzahl von Prozessen. Zweitens kehrt es nicht richtig zur Eingabeaufforderung zurück. Drittens war es nicht richtig, dass ich sagte, dass Jobs ungeschehen gemacht werden: Ich habe nur einen Indikator echo "DONE"nach der Schleife gesetzt, die ausgeführt wurde, bevor aktive Jobs nicht beendet wurden. => Das hat mich denken lassen, dass Jobs nicht erledigt wurden.
Mathe

Antworten:


15

Ein Makefile ist eine gute Lösung für Ihr Problem. Sie könnten diese parallele Ausführung in einer Shell programmieren, aber es ist schwierig, wie Sie bemerkt haben. Eine parallele Implementierung von make kümmert sich nicht nur um das Starten von Jobs und das Erkennen ihrer Beendigung, sondern kümmert sich auch um den Lastausgleich, was schwierig ist.

Die Anforderung für das Globbing ist kein Hindernis: Es gibt Implementierungen, die dies unterstützen. GNU make, das über eine Wildcard-Erweiterung wie z. B. $(wildcard *.c)Shell-Zugriff verfügt $(shell mycommand)(weitere Informationen finden Sie im GNU make-Handbuch). Dies ist die Standardeinstellung makeunter Linux und auf den meisten anderen Systemen verfügbar. Hier ist ein Makefile-Skelett, das Sie möglicherweise an Ihre Bedürfnisse anpassen können:

Quellen = $ (Platzhalter * .src)

all: $ (Quellen: .src = .tgt)

% .tgt: $ .src
    do_something $ <$$ (derivative_params $ <)> $ @

Führen Sie so etwas wie make -j4vier Jobs parallel aus oder make -j -l3halten Sie den Lastdurchschnitt bei 3.


8

Ich bin mir nicht sicher, wie Ihre abgeleiteten Argumente sind. Mit GNU Parallel http: // www.gnu.org/software/parallel/ können Sie dies tun, um einen Job pro CPU-Kern auszuführen:

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

Wenn Sie einfach die .extension ändern möchten, kann die {.} Nützlich sein:

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

Sehen Sie sich das Intro-Video zu GNU Parallel unter http://www.youtube.com/watch?v=OpaiGYxkSuQ an


7

Würde es nicht waitfür Sie funktionieren, den Befehl der Shell zu verwenden ?

for i in *
do
    do_something $i &
done
wait

Ihre Schleife führt einen Job aus, wartet darauf und führt dann den nächsten Job aus. Wenn die oben nicht funktioniert für Sie, dann könnte Ihnen besser funktionieren , wenn Sie bewegen pwaitnach done.


Nein, mit 1 Million Dateien würde ich 1 Million Prozesse ausführen, oder irre ich mich?
Mathe

1
@brubelsabs: Nun, es würde versuchen , eine Million Prozesse durchzuführen . Sie haben in Ihrer Frage nicht angegeben, wie viele Dateien Sie verarbeiten müssen. Ich würde denken, Sie müssten verschachtelte forSchleifen verwenden, um dies zu begrenzen: for file in *; do for i in {1..10}; do do_something "$i" & done; wait; done(ungetestet) Das sollte zehn auf einmal tun und warten, bis alle zehn jeder Gruppe fertig sind, bevor Sie mit den nächsten zehn beginnen. Ihre Schleife macht einen nach dem anderen, um den &Streit zu machen. Weitere Optionen finden Sie in der Frage, mit der JRobert verknüpft hat. Suchen Sie im Stapelüberlauf nach anderen Fragen, die Ihren (und diesen) ähnlich sind.
Bis auf weiteres angehalten.

Wenn das OP eine Million Dateien erwartet, hätte er ein Problem mit for i in *. Er würde Argumente mit einer Pipe oder so etwas an die Schleife übergeben müssen. Dann könnten Sie anstelle einer internen Schleife einen inkrementierenden Zähler ausführen und "micro-"wait"-s"jeden "$ ((i% 32))" -eq '0'

@ TennisWilliamson: Die Kombination waitmit einer inneren Gegenschleife hat bei mir gut funktioniert. Vielen Dank!
Joel Purra

3

Warum hat noch niemand Xargs erwähnt?

Angenommen, Sie haben genau drei Argumente.

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

Verwenden Sie andernfalls ein Trennzeichen (dafür ist null praktisch):

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

BEARBEITEN: Für die obigen Schritte sollte jeder Parameter durch ein Nullzeichen getrennt werden, und dann sollte die Anzahl der Parameter mit den xargs -n angegeben werden.


Ja, in unserem Projekt hatte jemand die gleiche Idee und es funktioniert auch unter Windows mit MSys hervorragend.
Mathe

0

Ich habe einige der Antworten ausprobiert. Sie machen das Skript etwas komplexer als nötig. Im Idealfall ist die Verwendung paralleloder xargswäre vorzuziehen, wenn die Operationen innerhalb der for-Schleife kompliziert sind. Es kann jedoch problematisch sein, Dateien mit großen und langen Zeilen zu erstellen, die parallel geliefert werden sollen. Stattdessen könnten wir source wie folgt verwenden

# 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

So würde für Ihr Problem die Lösung aussehen

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

definieren etwas tun als do_something.sh

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

}}

mit xargoder ausführengnu parallel

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

Ich gehe davon aus, dass die funktionale Unabhängigkeit von Iterationen von for impliziert ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.