Vier Aufgaben gleichzeitig ... wie mache ich das?

23

Ich habe ein paar PNG-Bilder in einem Verzeichnis. Ich habe eine Anwendung namens pngout, mit der ich diese Bilder komprimiere. Diese Anwendung wird von einem Skript aufgerufen, das ich erstellt habe. Das Problem ist, dass dieses Skript eines nach dem anderen ausführt:

FILES=(./*.png)
for f in  "${FILES[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 $f R${f/\.\//}
done

Die Verarbeitung von jeweils nur einer Datei nimmt viel Zeit in Anspruch. Nach dem Ausführen dieser App sehe ich, dass die CPU nur 10% beträgt. So entdeckte ich, dass ich diese Dateien in 4 Stapel aufteilen, jeden Stapel in ein Verzeichnis stellen und 4 aus vier Terminalfenstern vier Prozesse auslösen kann, so dass ich vier Instanzen meines Skripts gleichzeitig habe, die diese Bilder und das Skript verarbeiten Job dauert 1/4 der Zeit.

Das zweite Problem ist, dass ich Zeit verloren habe, die Bilder und Stapel aufzuteilen und das Skript in vier Verzeichnisse zu kopieren, 4 Terminalfenster zu öffnen, bla bla ...

Wie geht das mit einem Skript, ohne etwas teilen zu müssen?

Ich meine zwei Dinge: Erstens, wie kann ich aus einem Bash-Skript einen Prozess in den Hintergrund abfeuern? (Fügen Sie einfach & zum Ende hinzu?) Zweitens: Wie stoppe ich das Senden von Aufgaben an den Hintergrund, nachdem ich die vierten Aufgaben gesendet habe, und setze das Skript so lange, bis die Aufgaben beendet sind? Ich meine, nur eine neue Aufgabe in den Hintergrund zu schicken, wenn eine Aufgabe endet und immer 4 Aufgaben gleichzeitig erledigt sind? Wenn ich das nicht tue, wird die Schleife zig Millionen von Aufgaben im Hintergrund auslösen und die CPU wird verstopfen.

Weltraumhund
quelle
Siehe auch Parallelisieren einer for-Schleife
Gilles 'SO - hör auf, böse zu sein'

Antworten:

33

Wenn Sie eine Kopie davon haben xargs, die die parallele Ausführung mit unterstützt -P, können Sie dies einfach tun

printf '%s\0' *.png | xargs -0 -I {} -P 4 ./pngout -s0 {} R{}

Für andere Ideen enthält das Wooledge Bash-Wiki einen Abschnitt im Artikel zum Prozessmanagement, in dem genau beschrieben wird, was Sie möchten.

jw013
quelle
2
Es gibt auch "gnu parallel" und "xjobs", die für diesen Fall entwickelt wurden. Es ist meistens Geschmackssache, die Sie bevorzugen.
Wnoise
Könnten Sie bitte den vorgeschlagenen Befehl erläutern? Vielen Dank!
Eugene S
1
@EugeneS Könnten Sie etwas genauer wissen, was für ein Teil? Das printf sammelt alle png-Dateien und übergibt sie über eine Pipe an xargs, das Argumente von der Standardeingabe sammelt und sie zu Argumenten für den pngoutBefehl kombiniert, den das OP ausführen wollte. Die Schlüsseloption ist -P 4, die xargs anweist, bis zu 4 gleichzeitige Befehle zu verwenden.
JW013
2
Entschuldigung, dass Sie nicht präzise sind. Ich habe mich speziell dafür interessiert, warum Sie printfhier eher eine Funktion als eine normale Funktion verwendet haben ls .. | grep .. *.png. Auch die von xargsIhnen verwendeten Parameter ( -0und -I{}) haben mich interessiert . Vielen Dank!
Eugene S
3
@EugeneS Es ist für maximale Korrektheit und Robustheit. Dateinamen sind keine Zeilen und lskönnen nicht zum portablen und sicheren Parsen von Dateinamen verwendet werden . Die einzigen sicheren Zeichen zur Begrenzung von Dateinamen sind \0und /, da jedes andere Zeichen, einschließlich \n, Teil des Dateinamens sein kann. Die printfverwendet \0, um Dateinamen zu begrenzen, und die -0informiert xargsdarüber. Die -I{}Tells werden xargsdurch {}das Argument ersetzt.
JW013
8

Zusätzlich zu den bereits vorgeschlagenen Lösungen können Sie ein Makefile erstellen, in dem beschrieben wird, wie eine komprimierte Datei aus einer nicht komprimierten Datei erstellt und make -j 44 Jobs gleichzeitig ausgeführt werden. Das Problem ist, dass Sie komprimierte und unkomprimierte Dateien unterschiedlich benennen oder in unterschiedlichen Verzeichnissen speichern müssen, da sonst das Schreiben einer vernünftigen make-Regel unmöglich ist.

9000
quelle
5

So beantworten Sie Ihre beiden Fragen:

  • Ja, wenn Sie & am Ende der Zeile hinzufügen, wird die Shell angewiesen, einen Hintergrundprozess zu starten.
  • Mit dem waitBefehl können Sie die Shell auffordern, zu warten, bis alle Prozesse im Hintergrund abgeschlossen sind, bevor Sie fortfahren.

Hier ist das Skript so geändert, dass jes die Anzahl der Hintergrundprozesse protokolliert. Wenn dies NB_CONCURRENT_PROCESSESerreicht ist, wird das Skript jauf 0 zurückgesetzt und wartet, bis alle Hintergrundprozesse abgeschlossen sind, bevor die Ausführung fortgesetzt wird.

files=(./*.png)
nb_concurrent_processes=4
j=0
for f in "${files[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 "$f" R"${f/\.\//}" &
        ((++j == nb_concurrent_processes)) && { j=0; wait; }
done
Frederik Deweerdt
quelle
1
Dies wartet auf den letzten der vier gleichzeitigen Prozesse und startet dann einen Satz von weiteren vier. Vielleicht sollte man ein Array von vier PIDs aufbauen und dann auf diese spezifischen PIDs warten?
Nils
Um nur meine Korrekturen für den Code zu erklären: (1) Vermeiden Sie aus Gründen des Stils alle Namen von Großbuchstaben, da sie möglicherweise mit internen Shell-Variablen in Konflikt stehen. (2) Zusätzliche Anführungszeichen für $fusw. (3) Verwendung [für POSIX-kompatible Skripte, aber für reine Bash [[wird immer bevorzugt. In diesem Fall ((ist für die Arithmetik besser geeignet.
jw013