Parallele Schalenschleifen

14258
math

Ich möchte viele Dateien bearbeiten und da ich hier eine Reihe von Kernen habe, möchte ich es parallel machen:

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, müssen Sie nur die & nach den Jobs setzen und einen pwait-Aufruf ausführen. Der Parameter gibt die Anzahl der parallelen Prozesse an:

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

Dies funktioniert jedoch nicht sehr gut. Ich habe es zB mit einer for-Schleife versucht, die viele Dateien konvertiert, mir aber einen Fehler gab und die Aufträge rückgängig machte.

Ich kann nicht glauben, dass dies noch nicht geschehen ist, da die Diskussion auf der zsh-Mailingliste mittlerweile so alt ist. Wissen Sie es besser?

10
Ähnlich dieser Frage: http://superuser.com/questions/153630/running-commands-in-parallel-with-a-limit-of-simultaneous-number-of-commands Sehen Sie, ob diese Technik für Sie geeignet ist. JRobert vor 13 Jahren 0
Es wäre hilfreich, wenn Sie die Fehlermeldungen veröffentlicht haben. Dennis Williamson vor 13 Jahren 0
@JRobert Ja ich wusste das, aber das hilft nicht wirklich, da der Makefile-Ansatz nicht funktioniert, wie ich schon sagte! @Dennis: 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 habe ich gesagt, dass die Jobs nicht erledigt werden. Das war nicht richtig: Ich habe nach der Schleife, die ausgeführt wurde, bevor aktive Jobs noch nicht abgeschlossen sind, einen Indikator "echo" DONE "" gesetzt. => Das brachte mich zu der Annahme, dass keine Jobs gemacht wurden. math vor 13 Jahren 0

4 Antworten auf die Frage

14
Gilles

Ein Makefile ist eine gute Lösung für Ihr Problem. Sie können diese parallele Ausführung in einer Shell programmieren, aber es ist schwer, 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 auch um den Lastenausgleich, der schwierig ist.

Die Anforderung an Globbing ist kein Hindernis: Es gibt Implementierungen, die dies unterstützen. GNU make, das über Platzhaltererweiterung wie $(wildcard *.c)und Shell-Zugriff wie verfügt $(shell mycommand)(Funktionen zum Nachschlagen von Funktionen im GNU make-Handbuch für weitere Informationen). Dies ist der Standard makefür Linux und für die meisten anderen Systeme verfügbar. Hier ist ein Makefile-Skelett, das Sie möglicherweise an Ihre Bedürfnisse anpassen können:

sources = $ (Platzhalter * .src)  all: $ (sources: .src = .tgt)  % .tgt: $ .src do_something $ <$$ (abgeleitet_params $ <)> $ @ 

Führen Sie etwas aus make -j4, um beispielsweise vier Jobs parallel auszuführen oder make -j -l3die Last durchschnittlich um 3 zu halten.

7
Ole Tange

Ich bin nicht sicher, wie Ihre abgeleiteten Argumente aussehen. Mit GNU Parallel http: // www.gnu.org/software/parallel/ können Sie jedoch einen Job pro CPU-Kern ausführen:

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

Wenn Sie die abgeleitete Erweiterung einfach ändern möchten, kann {.} 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

6
Dennis Williamson

Würde der waitBefehl der Shell nicht für Sie funktionieren?

for i in * do do_something $i & done wait 

Ihre Schleife führt einen Job aus, wartet darauf und erledigt den nächsten Job. Wenn das Obige für Sie nicht funktioniert, funktionieren Ihre besser, wenn Sie sich pwaitdanach bewegen done.

Nein, bei 1 Million Dateien würde ich 1 Million Prozesse ausführen, oder irre ich mich? math vor 13 Jahren 0
@brubelsabs: Nun, es würde * versuchen *, eine Million Prozesse auszuführen. Sie haben in Ihrer Frage nicht gesagt, wie viele Dateien Sie verarbeiten müssen. Ich denke, Sie müssten geschachtelte `for`-Schleifen verwenden, um das einzuschränken:` for file in *; mache für i in ; do_something "$ i" & done; warten; done` (ungetestet) Das sollte zehn auf einmal machen und warten, bis alle zehn von jeder Gruppe fertig sind, bevor die nächsten zehn beginnen. Ihr Loop macht einen nach dem anderen, um den `&` Moot zu machen. Weitere Optionen finden Sie in der Frage, die mit ** JRobert ** verknüpft ist. Suchen Sie im Stack Overflow nach anderen Fragen, die Ihren (und denen) ähnlich sind. Dennis Williamson vor 13 Jahren 1
Wenn das OP eine Million Dateien erwartet, hätte er ein Problem mit `for i in *`. Er müsste Argumente mit einer Pipe oder so weiter an die Schleife übergeben. Dann könnten Sie anstelle einer internen Schleife einen inkrementierenden Zähler ausführen und "" micro- "wait" -s "" jedes "$ ((i% 32))" -eq '0' ausführen. vor 13 Jahren 0
@DennisWilliamson: Die Kombination von "wait" mit einer inneren Counter-Schleife hat für mich gut funktioniert. Vielen Dank! Joel Purra vor 9 Jahren 0
3
zebediah49

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 

Ansonsten verwenden Sie ein Trennzeichen (Null ist dafür 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 das oben Gesagte sollte jeder Parameter durch ein Nullzeichen getrennt werden, und dann sollte die Anzahl der Parameter mit xargs -n angegeben werden.

Ja, in unserem Projekt hatte jemand die gleiche Idee, und es funktioniert auch unter Windows mit MSys. math vor 12 Jahren 0