Erstellen eines einzelnen Ausgabestreams aus drei anderen parallel erzeugten Streams

10

Ich habe drei Arten von Daten, die in verschiedenen Formaten vorliegen. Für jeden Datentyp gibt es ein Python-Skript, das ihn in ein einheitliches Format umwandelt.

Dieses Python-Skript ist langsam und CPU-gebunden (an einen einzelnen Kern auf einem Multi-Core-Computer). Daher möchte ich drei Instanzen davon ausführen - eine für jeden Datentyp - und deren Ausgabe kombinieren, um sie weiterzuleiten sort. Grundsätzlich gleichbedeutend damit:

{ ./handle_1.py; ./handle_2.py; ./handle_3.py } | sort -n

Aber mit den drei Skripten, die parallel laufen.

Ich fand diese Frage, bei der GNU splitverwendet wurde, um einen Standard-Stream zwischen n Instanzen eines Skripts, das den Stream verarbeitet, abzurunden.

Aus der geteilten Manpage:

-n, --number=CHUNKS
          generate CHUNKS output files.  See below
CHUNKS  may be:
 N       split into N files based on size of input
 K/N     output Kth of N to stdout
 l/N     split into N files without splitting lines
 l/K/N   output Kth of N to stdout without splitting lines
 r/N     like 'l'  but  use  round  robin  distributio

Der r/NBefehl impliziert also " ohne Linien zu teilen ".

Auf dieser Grundlage scheint die folgende Lösung machbar zu sein:

split -n r/3 -u --filter="./choose_script" << EOF
> 1
> 2
> 3
> EOF

Wo choose_scriptmacht das:

#!/bin/bash
{ read x; ./handle_$x.py; }

Leider sehe ich eine Vermischung von Zeilen - und viele neue Zeilen, die nicht vorhanden sein sollten.

Wenn ich beispielsweise meine Python-Skripte durch einige einfache Bash-Skripte ersetze, die dies tun:

#!/bin/bash
# ./handle_1.sh
while true; echo "1-$RANDOM"; done;

.

#!/bin/bash
# ./handle_2.sh
while true; echo "2-$RANDOM"; done;

.

#!/bin/bash
# ./handle_3.sh
while true; echo "3-$RANDOM"; done;

Ich sehe diese Ausgabe:

1-8394

2-11238
2-22757
1-723
2-6669
3-3690
2-892
2-312511-24152
2-9317
3-5981

Dies ist ärgerlich - basierend auf dem oben eingefügten Manpage-Auszug sollte die Zeilenintegrität erhalten bleiben.

Natürlich funktioniert es, wenn ich das -uArgument entferne , aber dann ist es gepuffert und mir geht der Speicher aus, da es die Ausgabe aller Skripte bis auf eines puffert.

Wenn jemand hier einen Einblick hat, wäre er sehr dankbar. Ich bin hier überfordert.

Cera
quelle
Einige Leute in #bash on freenode schlugen vor, dass ich alle drei Prozesse spawne und sie im Hintergrund schreibe, in benutzerdefinierte FDs schreibe, dann diese FDs durchlaufe und Zeilen für sie lese, aber ich habe nicht herausgefunden, wie ich das praktikabel machen kann. Mir wurde auch gesagt, ich solle mir das coproceingebaute Bash ansehen, obwohl ich nicht wirklich sehe, wie es zutrifft.
Cera
1
Müssen Sie es ohne Zwischendateien tun? Könnten Sie nicht einfach tun job1.py > file1 & job2.py > file 2 & job3.py > file3 ; wait ; sort -n file1 file2 file3?
Angus

Antworten:

2

Versuchen Sie es mit der Option -u von GNU parallel.

echo "1\n2\n3" | parallel -u -IX ./handle_X.sh

Dadurch werden sie parallel ausgeführt, ohne dass der gesamte Prozess gepuffert wird.

Flowblok
quelle
Ich bin ein wenig verwirrt - ist das Xin IXsagen , -Idass X die Flagge für den Ersatz sein wird, oder ist es die Anwendung -XFlagge, die scheinbar auch eine relevante Bedeutung hat?
Cera
Hmph. Ich mache das: parallel -u -X ./handle_{}.sh ::: "1" "2" "3"und leider sehe ich immer noch einiges an Mangeln der Ausgabe.
Cera
Ersteres: Sie können es auch verwenden parallel -u ./handle_{}.sh, aber ich ziehe es vor, es zu ändern, da geschweifte Klammern auch die Bedeutung haben, Befehle zusammenzufügen (wie in Ihrer Frage).
Flowblok
Scheint für mich zu funktionieren, mein grep nimmt kein Mangeln auf: pastie.org/5113187 (verwenden Sie die Test-Bash-Skripte oder Ihre eigentlichen Python-Skripte?)
flowblok
Das Problem ist, dass das eigentlich nichts parallel macht. Ich benutze die Bash-Skripte - pastie.org/5113225
Cera
2

Versuchen:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py

Wenn handle_1.pyein Dateiname verwendet wird:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

Sie möchten nicht, dass die Ausgabe gemischt wird, verwenden Sie also nicht -u.

Wenn Sie die Reihenfolge beibehalten möchten (daher liegt die gesamte Ausgabe von handle_1 vor handle_2 und Sie können möglicherweise das Sortieren vermeiden):

parallel -k  ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

Wenn Sie es dennoch sortieren möchten, können Sie die Sortierung parallelisieren und Folgendes verwenden sort -m:

parallel --files "./handle_{1}.py {2} | sort -n"  ::: 1 2 3 ::: files* | parallel -j1 -X sort -m

Setzen Sie $ TMPDIR auf ein Verzeichnis, das groß genug ist, um die Ausgabe aufzunehmen.

Ole Tange
quelle
1
Ich möchte, dass die Ausgabe 'gemischt' wird - ich möchte nur sicherstellen, dass jede Zeile in der endgültigen Ausgabe eine einzelne Zeile aus einem der Unterprozesse ist. Wenn ich es nicht mische, geht dem System der Speicher aus, der die noch nicht ausgedruckten Standard-Streams puffert.
Cera
Mit GNU Parallel wird Ihnen nicht der Speicher ausgehen: Es wird nicht im Speicher gepuffert. Warum puffert es Ihrer Meinung nach im Speicher?
Ole Tange
2

Vielleicht fehlt mir etwas, aber kannst du nicht einfach Folgendes tun:

(./handle_1.py & ./handle_2.py & ./handle_3.py) | sort -n

Wenn Sie möchten, dass Zeilen von jedem Prozess nicht verschachtelt werden, ist es wahrscheinlich einfacher, sicherzustellen, dass der Prozess sie vollständig schreibt und möglicherweise die Ausgabepufferung deaktiviert, da writes in eine Pipe garantiert atomar sind, solange sie nicht größer als sind PIPE_BUF. Sie können beispielsweise sicherstellen, dass die Ausgabepufferung à la stdioand call verwendet wird fflushoder was auch immer das Äquivalent ist, pythonnachdem eine oder mehrere Zeilen geschrieben wurden.

Wenn Sie die Python-Skripte nicht ändern können, haben Sie folgende Möglichkeiten:

lb() { grep --line-buffered '^'; }

(mit GNU grep) oder:

lb() while IFS= read -r l; do printf '%s\n' "$l"; done

(Siehe Hinweise in den Kommentaren unten, wenn es sich bei der Ausgabe der Befehle nicht um Text handelt.)

Und TU:

(./handle_1.py | lb & ./handle_2.py | lb & ./handle_3.py | lb) | sort -n

Eine andere Möglichkeit, diese drei lbProzesse zu vermeiden , besteht darin, drei Pipes zu einem Befehl zu haben, der select/ verwendet, um pollzu sehen, woher eine Ausgabe kommt, und sie sortzeilenbasiert zuzuführen, aber es erfordert ein wenig Programmierung.

Stéphane Chazelas
quelle
Du brauchst waitda drin, denke ich.
Derobert
1
Nein, es sei denn, einige der Programme schließen ihre Standardausgabe vor dem Beenden, da die Pipe und sort -nso lange bestehen bleiben, bis alle Programme, auf denen ein fd geöffnet ist, beendet wurden.
Stéphane Chazelas
In der Tat, ich habe getestet, sind Sie richtig.
Derobert
Nein, ich bekomme immer noch eine verstümmelte Ausgabe. Linien werden miteinander vermischt und verschachtelt.
Cera
1
OK @Cerales, siehe meine aktualisierte Antwort
Stéphane Chazelas
1

Flowboks Antwort war die richtige Lösung. Seltsamerweise wird die Ausgabe von GNU parallelbeschädigt, wenn sie direkt in eine Datei ausgegeben wird - aber nicht, wenn sie in eine Datei geht.

Glücklicherweise script -cist verfügbar, um eine tty nachzuahmen.

Es gibt noch die drei Skripte:

#!/bin/bash
# handle_1.sh
while true; do echo "1-$RANDOM$RANDOM$RANDOM$RANDOM"; done

.

#!/bin/bash
# handle_2.sh
while true; do echo "2-$RANDOM$RANDOM$RANDOM$RANDOM"; done

.

#!/bin/bash
# handle_3.sh
while true; do echo "3-$RANDOM$RANDOM$RANDOM$RANDOM"; done

Dann gibt es eine Datei, die den Aufruf von parallel kapselt:

#!/bin/bash
# run_parallel.sh
parallel -u -I N ./handle_N.sh ::: "1" "2" "3"

Und dann nenne ich es so:

script -c ./run_parallel > output

Die Zeilen in der Ausgabe werden zeilenweise zwischen den Ausgaben der verschiedenen Skripte gemischt, aber sie werden in einer bestimmten Zeile nicht entstellt oder verschachtelt.

Bizarres Verhalten von parallel- Ich kann einen Fehlerbericht einreichen.

Cera
quelle