Ich habe eine Reihe von PNG-Bildern in einem Verzeichnis. Ich habe eine Anwendung namens pngout, die ich ausführe, um diese Bilder zu komprimieren. Diese Anwendung wird von einem Skript aufgerufen, das ich erstellt habe. Das Problem ist, dass dieses Skript jeweils ein Bild nach dem anderen ausführt, ungefähr so:
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 jeweils nur einer Datei nimmt viel Zeit in Anspruch. Nachdem ich diese App ausgeführt habe, sehe ich, dass die CPU nur 10 % ausmacht. Also habe ich herausgefunden, dass ich diese Dateien in 4 Stapel aufteilen, jeden Stapel in ein Verzeichnis legen und 4 Prozesse aus vier Terminalfenstern starten kann, sodass ich vier Instanzen meines Skripts gleichzeitig habe, die diese Bilder verarbeiten, und der Job dauert 1/4 der Zeit.
Das zweite Problem ist, dass ich Zeit verloren habe, indem ich die Bilder und Stapel aufgeteilt und das Skript in vier Verzeichnisse kopiert, vier Terminalfenster geöffnet und so weiter und so fort habe …
Wie geht das mit einem Skript, ohne etwas aufteilen zu müssen?
Damit meine ich zwei Dinge: Erstens, wie kann ich von einem Bash-Skript aus einen Prozess in den Hintergrund schicken? (einfach & ans Ende anhängen?) Zweitens: Wie beende ich das Senden von Tasks in den Hintergrund, nachdem ich die vierte Task gesendet habe, und wie kann ich das Skript so einstellen, dass es wartet, bis die Tasks beendet sind? Ich meine, einfach eine neue Task in den Hintergrund schicken, wenn eine Task beendet ist, und dabei immer 4 Tasks parallel laufen lassen? Wenn ich das nicht mache, schickt die Schleife Millionen von Tasks in den Hintergrund und die CPU wird verstopft.
Antwort1
Wenn Sie eine Kopie von haben, xargs
die die parallele Ausführung mit unterstützt -P
, können Sie einfach Folgendes tun:
printf '%s\0' *.png | xargs -0 -I {} -P 4 ./pngout -s0 {} R{}
Für weitere Ideen gibt es im Wooledge Bash Wiki eineAbschnittim Artikel „Prozessmanagement“ wird genau beschrieben, was Sie möchten.
Antwort2
Zusätzlich zu den bereits vorgeschlagenen Lösungen können Sie ein Makefile erstellen, das beschreibt, wie aus einer unkomprimierten Datei eine komprimierte Datei erstellt wird, und damit make -j 4
4 Jobs parallel ausführen. Das Problem besteht darin, dass Sie komprimierte und unkomprimierte Dateien unterschiedlich benennen oder in unterschiedlichen Verzeichnissen speichern müssen, da es sonst unmöglich ist, eine sinnvolle Make-Regel zu schreiben.
Antwort3
Wenn Sie GNU Parallel habenhttp://www.gnu.org/software/parallel/installiert haben, können Sie Folgendes tun:
parallel ./pngout -s0 {} R{} ::: *.png
Sie können GNU Parallel einfach wie folgt installieren:
wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem
Sehen Sie sich die Einführungsvideos zu GNU Parallel an, um mehr zu erfahren: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Antwort4
Um Ihre beiden Fragen zu beantworten:
- ja, das Hinzufügen von & am Ende der Zeile weist Ihre Shell an, einen Hintergrundprozess zu starten.
- Mit dem
wait
Befehl können Sie die Shell anweisen, zu warten, bis alle Prozesse im Hintergrund abgeschlossen sind, bevor sie fortfährt.
Hier ist das so geänderte Skript, dass j
es dazu dient, die Anzahl der Hintergrundprozesse zu verfolgen. Wenn diese Zahl NB_CONCURRENT_PROCESSES
erreicht ist, wird das Skript j
auf 0 zurückgesetzt und wartet, bis alle Hintergrundprozesse abgeschlossen sind, bevor es seine Ausführung fortsetzt.
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