Frage Parallele Bash-Skript mit maximaler Anzahl von Prozessen


Sagen wir, ich habe eine Schleife in Bash:

for foo in `some-command`
do
   do-something $foo
done

do-something ist cpu gebunden und ich habe einen schönen glänzenden 4-Core-Prozessor. Ich würde gerne in der Lage sein, bis 4 zu laufen do-somethingist sofort.

Der naive Ansatz scheint zu sein:

for foo in `some-command`
do
   do-something $foo &
done

Dies wird ausgeführt alle  do-somethings sofort, aber es gibt ein paar Nachteile, vor allem, dass etwas tun kann etwas signifikante I / O, die Durchführung alle sofort könnte etwas verlangsamen. Das andere Problem ist, dass dieser Code-Block sofort zurückkehrt, also keine Möglichkeit, andere Arbeiten zu erledigen, wenn alle do-somethings sind fertig.

Wie würdest du diese Schleife schreiben, also gibt es immer X do-somethingläuft auf einmal?


76
2017-09-01 16:47


Ursprung


Antworten:


Je nachdem, was Sie tun möchten, können auch Xargs helfen (hier: Konvertieren von Dokumenten mit pdf2ps):

cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps

Aus den Dokumenten:

--max-procs=max-procs
-P max-procs
       Run up to max-procs processes at a time; the default is 1.
       If max-procs is 0, xargs will run as many processes as  possible  at  a
       time.  Use the -n option with -P; otherwise chances are that only one
       exec will be done.

52
2018-05-19 07:50



Mit GNU Parallel http://www.gnu.org/software/parallel/ Du kannst schreiben:

some-command | parallel do-something

GNU Parallel unterstützt auch laufende Jobs auf entfernten Computern. Dadurch wird auf den Remotecomputern ein Prozessorkern pro CPU-Kern ausgeführt - selbst wenn sie eine unterschiedliche Anzahl von Prozessorkernen haben:

some-command | parallel -S server1,server2 do-something

Ein fortgeschrittenes Beispiel: Hier listen wir Dateien auf, auf denen my_script laufen soll. Dateien haben eine Erweiterung (möglicherweise .jpeg). Wir möchten, dass die Ausgabe von my_script neben die Dateien in basename.out gestellt wird (z. B. foo.jpeg -> foo.out). Wir möchten my_script einmal für jeden Core ausführen, den der Computer hat, und wir möchten ihn auch auf dem lokalen Computer ausführen. Für die Remote-Computer soll die zu verarbeitende Datei an den angegebenen Computer übertragen werden. Wenn my_script beendet ist, wollen wir foo.out zurück übertragen und wir wollen dann foo.jpeg und foo.out vom entfernten Computer entfernen:

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"

GNU Parallel stellt sicher, dass die Ausgabe von jedem Job nicht gemischt wird, so dass Sie die Ausgabe als Eingabe für ein anderes Programm verwenden können:

some-command | parallel do-something | postprocess

In den Videos finden Sie weitere Beispiele: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


35
2018-06-10 01:37



maxjobs = 4
parallelisieren () {
        während [$ # -gt 0]; machen
                jobcnt = (`jobs -p`)
                if [$ {# jobcnt [@]} -lt $ maxjobs]; dann
                        Mach etwas $ 1 &
                        Verschiebung
                sonst
                        Schlaf 1
                fi
        erledigt
        warten
}

parallelisieren arg1 arg2 "5 args zum dritten Job" arg4 ...

22
2017-09-01 18:00



Verwenden Sie anstelle einer einfachen Bash ein Makefile und geben Sie die Anzahl der gleichzeitigen Jobs an make -jX Wobei X die Anzahl der Jobs ist, die gleichzeitig ausgeführt werden.

Oder du kannst es benutzen wait ("man wait"): Starten Sie mehrere untergeordnete Prozesse, rufen Sie an wait - Es wird beendet, wenn die Kindprozesse abgeschlossen sind.

maxjobs = 10

foreach line in `cat file.txt` {
 jobsrunning = 0
 while jobsrunning < maxjobs {
  do job &
  jobsrunning += 1
 }
wait
}

job ( ){
...
}

Wenn Sie das Ergebnis des Jobs speichern müssen, weisen Sie das Ergebnis einer Variablen zu. Nach wait Sie überprüfen nur, was die Variable enthält.


11
2017-09-01 16:50



Vielleicht versuchen Sie ein Parallelisierungs-Dienstprogramm anstatt die Schleife neu zu schreiben? Ich bin ein großer Fan von xjobs. Ich verwende xjobs die ganze Zeit, um Dateien in unserem Netzwerk zu kopieren, normalerweise wenn ein neuer Datenbankserver eingerichtet wird. http://www.maier-komor.de/xjobs.html


8
2017-09-01 16:55



Hier eine alternative Lösung, die in .bashrc eingefügt und für einen täglichen Liner verwendet werden kann:

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

Um es zu benutzen, muss man nur tun & 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

Es wäre schöner zu benutzen wait anstatt auf die Ausgabe von beschäftigt zu warten jobs -p, aber es scheint keine offensichtliche Lösung zu sein, zu warten, bis einer der gegebenen Jobs beendet ist, anstatt alle von ihnen.


8
2018-05-19 03:40



Während du das richtig machst bash ist wahrscheinlich unmöglich, du kannst ein halb-recht ziemlich leicht machen. bstark gab eine faire Annäherung an das Recht, aber sein hat die folgenden Fehler:

  • Wortteilung: Sie können keine Jobs an sie übergeben, die eines der folgenden Zeichen in ihren Argumenten verwenden: Leerzeichen, Tabulatoren, Zeilenumbrüche, Sterne, Fragezeichen. Wenn Sie das tun, werden die Dinge möglicherweise unerwartet brechen.
  • Es beruht auf dem Rest Ihres Skripts, um nichts Hintergrundwissen zu haben. Wenn Sie dies tun oder später etwas zum Script hinzufügen, das im Hintergrund gesendet wird, weil Sie vergessen haben, dass Sie aufgrund seines Snippets keine Hintergrundjobs verwenden dürfen, werden die Dinge abbrechen.

Eine andere Annäherung, die diese Fehler nicht aufweist, ist die folgende:

scheduleAll() {
    local job i=0 max=4 pids=()

    for job; do
        (( ++i % max == 0 )) && {
            wait "${pids[@]}"
            pids=()
        }

        bash -c "$job" & pids+=("$!")
    done

    wait "${pids[@]}"
}

Beachten Sie, dass dieses Programm leicht angepasst werden kann, um auch den Beendigungscode jedes Jobs zu überprüfen, wenn dieser beendet wird, damit Sie den Benutzer warnen können, wenn ein Job fehlschlägt, oder einen Exit-Code für diesen Job festlegen scheduleAll entsprechend der Anzahl der Jobs, die fehlgeschlagen sind, oder so.

Das Problem mit diesem Code ist nur das:

  • Es plant vier (in diesem Fall) Jobs gleichzeitig und wartet dann auf alle vier, um zu enden. Einige können früher als andere ausgeführt werden, was dazu führt, dass der nächste Stapel von vier Jobs wartet, bis der längste des vorherigen Stapels fertig ist.

Eine Lösung, die sich um dieses letzte Problem kümmert, müsste nutzen kill -0 abfragen, ob irgendwelche Prozesse verschwunden sind statt der wait und planen Sie den nächsten Job. Dies bringt jedoch ein kleines neues Problem mit sich: Sie haben eine Wettlaufsituation zwischen einer Jobendung und der kill -0 Überprüfen, ob es beendet ist. Wenn der Job beendet wurde und ein anderer Prozess auf Ihrem System zur gleichen Zeit gestartet wird, wird eine zufällige PID genommen, die zufällig die des gerade beendeten Jobs ist kill -0 merke nicht, dass deine Arbeit beendet ist und die Dinge werden wieder brechen.

Eine perfekte Lösung ist nicht möglich bash.


6
2018-05-19 07:26



Wenn Sie mit dem vertraut sind make In den meisten Fällen können Sie die Liste der Befehle, die Sie als Makefile ausführen möchten, ausdrücken. Wenn Sie beispielsweise $ SOME_COMMAND für Dateien * .input ausführen müssen, von denen jede * .output erzeugt, können Sie das Makefile verwenden

INPUT = a.input b.input
OUTPUT = $ (INPUT: .input = .output)

%.Ausgang Eingang
    $ (SOME_COMMAND) $ <$ @

alles: $ (AUSGABE)

und dann lauf einfach

make -j <NUMMER>

um maximal NUMBER Befehle parallel laufen zu lassen.


5
2018-05-21 20:33



Funktion für bash:

parallel ()
{
    awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\t@-\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make $@ -f - all
}

mit:

cat my_commands | parallel -j 4

3
2018-02-22 10:14



Das Projekt, an dem ich arbeite, nutzt das warten Befehl, um parallele Shell (ksh eigentlich) Prozesse zu steuern. Um Ihre Bedenken hinsichtlich IO auf einem modernen Betriebssystem zu berücksichtigen, ist es möglich, dass die parallele Ausführung die Effizienz erhöht. Wenn alle Prozesse die gleichen Blöcke auf der Festplatte lesen, muss nur der erste Prozess die physische Hardware treffen. Die anderen Prozesse können den Block häufig aus dem Festplatten-Cache des Betriebssystems im Speicher abrufen. Offensichtlich ist das Lesen aus dem Speicher mehrere Größenordnungen schneller als das Lesen von der Platte. Außerdem erfordert der Vorteil keine Kodierungsänderungen.


2
2017-09-03 23:19