2009-10-08 16 views
21

Gibt es eine einfache Möglichkeit, die Anzahl der gleichzeitigen Jobs in bash zu begrenzen? Damit meine ich, den Block & zu machen, wenn mehr als n gleichzeitige Jobs im Hintergrund laufen.Bash: Begrenzung der Anzahl gleichzeitiger Jobs?

Ich weiß, dass ich dies mit ps | implementieren kann grep-style Tricks, aber gibt es einen einfacheren Weg?

+1

Ich denke, diese Frage könnte Ihnen helfen: http://StackOverflow.com/Questions/38160/Parallelize-Bash-Skript –

+1

So viele verworrene Antworten, aber keine Möglichkeit zu sagen Bash "maximal zehn gleichzeitige Jobs!". Ich nehme an, da ist keiner. Schade, das wäre wirklich ein schönes Feature. –

Antwort

15

Wenn Sie GNU Parallel http://www.gnu.org/software/parallel/ installiert, dies zu tun:

parallel gzip ::: *.log 

die eine gzip pro CPU-Kern laufen wird, bis alle Logfiles gzipped werden.

Wenn es Teil einer größeren Schleife Sie sem stattdessen verwenden können:

for i in *.log ; do 
    echo $i Do more stuff here 
    sem -j+0 gzip $i ";" echo done 
done 
sem --wait 

Es wird das gleiche tun, sondern gibt Ihnen eine Chance für jede Datei mehr Dinge zu tun.

(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash 

Es wird herunterladen, überprüfen Signatur und eine persönliche Installation tun, wenn es nicht global installieren:

Wenn GNU Parallel für Ihre Distribution nicht verpackt ist, können Sie GNU Parallel einfach durch installieren.

Uhr die Introvideos für GNU Parallel mehr zu erfahren: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

+2

Das ist erstaunlich - der parallele Befehl ist auch großartig, Sie müssen nicht einmal die Schleife machen. – frabcus

+0

Die ':::' -Syntax ist veraltet, obwohl es eine Option gibt, die es für die Abwärtskompatibilität aktiviert, die einige Distributionen standardmäßig aktivieren (etwas seltsam, weil dann die Beispiele im Handbuch nicht sofort funktionieren). – tripleee

+2

@tripleee ::: wird seit 2010722 unterstützt und wird in absehbarer Zeit sein. Ihre Installation könnte jedoch versuchen, Tollefs Parallele nachzuahmen, ohne es Ihnen zu sagen - was erklärt, warum Sie es seltsam finden. Das Entfernen von/etc/parallel/config sollte das Problem beheben. –

12

Ein kleines Bash-Skript könnte Ihnen helfen:

# content of script exec-async.sh 
joblist=($(jobs -p)) 
while ((${#joblist[*]} >= 3)) 
do 
    sleep 1 
    joblist=($(jobs -p)) 
done 
$* & 

Wenn Sie rufen:

. exec-async.sh sleep 10 

... viermal, werden die ersten drei Anrufe sofort zurück, wird der vierte Anruf blockiert, bis Es laufen weniger als drei Jobs.

Sie müssen dieses Skript in der aktuellen Sitzung starten, indem Sie ihm . voranstellen, da jobs nur die Jobs der aktuellen Sitzung auflistet.

Die sleep innen ist hässlich, aber ich habe keinen Weg gefunden, auf den ersten Job zu warten, der endet.

+0

die Kinderprozesse werden zu Zombies. Irgendwo sollte eine Wartezeit eintreten. – torbatamas

0

Haben Sie darüber nachgedacht, zehn langwierige Listener-Prozesse zu starten und mit ihnen über Named Pipes zu kommunizieren?

0

Sie ulimit -u verwenden können, sehen http://ss64.com/bash/ulimit.html

+1

Das einzige Problem damit ist, dass die Prozesse sterben, anstatt zu blockieren und zu warten, was das gewünschte Verhalten ist. – Benj

+1

Diese Lösung ist gefährlich und schwer zu kontrollieren. Da meine Shell-Skripte dazu neigen, viele Erweiterungen und Rohrleitungen zu enthalten, benötigt jede Zeile normalerweise mehr als 4 Prozesse. Wenn Sie den ulimit-Wert für den gesamten Prozess festlegen, wird nicht nur die Anzahl der ausgeführten Jobs begrenzt, sondern es werden auch die für die Ausführung des restlichen Skripts erforderlichen Dinge eingeschränkt, sodass die Dinge auf unvorhersehbare Weise blockiert/fehlschlagen. – amphetamachine

3

Wenn Sie bereit sind, diese außerhalb der reinen bash zu tun, sollten Sie einen Job Queuing-System suchen.

Zum Beispiel gibt es GNU queue oder PBS. Und für PBS können Sie in Maui für die Konfiguration suchen.

Beide Systeme erfordern eine Konfiguration, aber es ist durchaus möglich, eine bestimmte Anzahl von Jobs gleichzeitig auszuführen und nur neu in der Warteschlange befindliche Jobs zu starten, wenn ein laufender Job beendet wird. In der Regel werden diese Jobwarteschlangensysteme in Supercomputing-Clustern verwendet, in denen Sie einem bestimmten Batch-Job eine bestimmte Menge an Arbeitsspeicher oder Rechenzeit zuweisen möchten. Es gibt jedoch keinen Grund, warum Sie einen dieser Computer ohne Rücksicht auf die Rechenzeit oder die Speicherbegrenzungen nicht auf einem einzelnen Desktop-Computer verwenden können.

16

Das folgende Skript zeigt eine Möglichkeit, dies mit Funktionen zu tun. Sie können entweder legen Sie die bgxupdate und bgxlimit Funktionen in Ihrem Skript oder sie in einer separaten Datei, die von Ihrem Skript mit bezogen ist:

. /path/to/bgx.sh 

Es hat den Vorteil, dass Sie unabhängig mehrere Gruppen von Prozessen aufrechterhalten kann (man kann Führen Sie zum Beispiel eine Gruppe mit einem Limit von 10 und eine andere völlig separate Gruppe mit einem Limit von 3).

Es verwendet bash Built-in, jobs, um eine Liste der Unterprozesse zu erhalten, aber sie in einzelnen Variablen verwaltet. In der Schleife unten können Sie sehen, wie Sie die bgxlimit-Funktion aufrufen:

  • eine leere Gruppenvariable einrichten.
  • übertragen Sie das auf bgxgrp.
  • Anruf bgxlimit mit dem Limit und Befehl, den Sie ausführen möchten.
  • übertragen Sie die neue Gruppe zurück auf Ihre Gruppenvariable.
  • Natürlich, wenn Sie nur eine Gruppe haben, verwenden Sie einfach bgxgrp direkt, anstatt hinein und heraus zu übertragen.

    #!/bin/bash 
    
    # bgxupdate - update active processes in a group. 
    # Works by transferring each process to new group 
    # if it is still active. 
    # in: bgxgrp - current group of processes. 
    # out: bgxgrp - new group of processes. 
    # out: bgxcount - number of processes in new group. 
    
    bgxupdate() { 
        bgxoldgrp=${bgxgrp} 
        bgxgrp="" 
        ((bgxcount = 0)) 
        bgxjobs=" $(jobs -pr | tr '\n' ' ')" 
        for bgxpid in ${bgxoldgrp} ; do 
         echo "${bgxjobs}" | grep " ${bgxpid} " >/dev/null 2>&1 
         if [[ $? -eq 0 ]] ; then 
          bgxgrp="${bgxgrp} ${bgxpid}" 
          ((bgxcount = bgxcount + 1)) 
         fi 
        done 
    } 
    
    # bgxlimit - start a sub-process with a limit. 
    
    # Loops, calling bgxupdate until there is a free 
    # slot to run another sub-process. Then runs it 
    # an updates the process group. 
    # in: $1  - the limit on processes. 
    # in: $2+ - the command to run for new process. 
    # in: bgxgrp - the current group of processes. 
    # out: bgxgrp - new group of processes 
    
    bgxlimit() { 
        bgxmax=$1 ; shift 
        bgxupdate 
        while [[ ${bgxcount} -ge ${bgxmax} ]] ; do 
         sleep 1 
         bgxupdate 
        done 
        if [[ "$1" != "-" ]] ; then 
         $* & 
         bgxgrp="${bgxgrp} $!" 
        fi 
    } 
    
    # Test program, create group and run 6 sleeps with 
    # limit of 3. 
    
    group1="" 
    echo 0 $(date | awk '{print $4}') '[' ${group1} ']' 
    echo 
    for i in 1 2 3 4 5 6 ; do 
        bgxgrp=${group1} ; bgxlimit 3 sleep ${i}0 ; group1=${bgxgrp} 
        echo ${i} $(date | awk '{print $4}') '[' ${group1} ']' 
    done 
    
    # Wait until all others are finished. 
    
    echo 
    bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp} 
    while [[ ${bgxcount} -ne 0 ]] ; do 
        oldcount=${bgxcount} 
        while [[ ${oldcount} -eq ${bgxcount} ]] ; do 
         sleep 1 
         bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp} 
        done 
        echo 9 $(date | awk '{print $4}') '[' ${group1} ']' 
    done 
    

    Hier ist ein Probelauf:

    0 12:38:00 [ ] 
    
    1 12:38:00 [ 3368 ] 
    2 12:38:00 [ 3368 5880 ] 
    3 12:38:00 [ 3368 5880 2524 ] 
    4 12:38:10 [ 5880 2524 1560 ] 
    5 12:38:20 [ 2524 1560 5032 ] 
    6 12:38:30 [ 1560 5032 5212 ] 
    
    9 12:38:50 [ 5032 5212 ] 
    9 12:39:10 [ 5212 ] 
    9 12:39:30 [ ] 
    
    • Das Ganze beginnt bei 12.38.00 und, wie Sie sehen können, die ersten drei Prozesse sofort ausführen.
    • Jeder Prozess schläft für n*10 Sekunden, so dass der vierte Prozess erst bei den ersten Exits beginnt (zur Zeit t = 10 oder 12:38:10). Sie können sehen, dass der Prozess 3368 aus der Liste verschwunden ist, bevor 1560 hinzugefügt wurde.
    • Ähnlich beginnt der fünfte Prozess (5032), wenn der zweite (5880) zum Zeitpunkt t = 20 austritt.
    • Und schließlich beginnt der sechste Prozess (5212), wenn der dritte (2524) zum Zeitpunkt t = 30 ausläuft.
    • Dann beginnt der Ablauf, vierter Prozess endet um t = 50 (begonnen bei 10, Dauer von 40), fünft bei t = 70 (gestartet bei 20, Dauer von 50) und sechster bei t = 90 (gestartet um 30, Dauer von 60).

    Oder in Form Zeit online:

    Process: 1 2 3 4 5 6 
    -------- - - - - - - 
    12:38:00^^^
    12:38:10 v | |^
    12:38:20  v | |^
    12:38:30  v | |^
    12:38:40   | | | 
    12:38:50   v | | 
    12:39:00    | | 
    12:39:10    v | 
    12:39:20     | 
    12:39:30     v 
    
    +0

    Sehr schön, danke! –

    5

    Dies könnte für die meisten Zwecke gut genug sein, aber nicht optimal ist.

    #!/bin/bash 
    
    n=0 
    maxjobs=10 
    
    for i in *.m4a ; do 
        # (DO SOMETHING) & 
    
        # limit jobs 
        if (($(($((++n)) % $maxjobs)) == 0)) ; then 
         wait # wait until all have finished (not optimal, but most times good enough) 
         echo $n wait 
        fi 
    done 
    
    +0

    Was ist nicht optimal? – naught101

    +4

    Sie starten 10 Jobs und warten dann auf alle 10, bevor Sie weitere 10 Jobs starten. Manchmal läuft nur ein Job statt 10. Das ist nicht gut, wenn Sie langsame und schnelle Jobs zusammen gemischt haben. – cat

    6

    Sie Angenommen, möchten Code wie folgt schreiben:

    for x in $(seq 1 100); do  # 100 things we want to put into the background. 
        max_bg_procs 5   # Define the limit. See below. 
        your_intensive_job & 
    done 
    

    Wo max_bg_procs sollten in Ihrer .bashrc gesetzt werden:

    function max_bg_procs { 
        if [[ $# -eq 0 ]] ; then 
          echo "Usage: max_bg_procs NUM_PROCS. Will wait until the number of background (&)" 
          echo "   bash processes (as determined by 'jobs -pr') falls below NUM_PROCS" 
          return 
        fi 
        local max_number=$((0 + ${1:-0})) 
        while true; do 
          local current_number=$(jobs -pr | wc -l) 
          if [[ $current_number -lt $max_number ]]; then 
            break 
          fi 
          sleep 1 
        done 
    } 
    
    +1

    Ich stellte fest, dass ich "jobs -pr" anstelle von "jobs-p" verwenden musste, sonst beendete es nie den letzten Job und ging nicht über den ersten Job hinaus, wenn ich das Limit auf 1 Job auf einmal festlegte. – BenjaminBallard

    0

    Unter Linux verwende ich dies, um die bash Jobs auf die Anzahl der verfügbaren CPUs (pos durch Setzen der CPU_NUMBER) überschrieben werden.

    [ "$CPU_NUMBER" ] || CPU_NUMBER="`nproc 2>/dev/null || echo 1`" 
    
    while [ "$1" ]; do 
        { 
         do something 
         with $1 
         in parallel 
    
         echo "[$# items left] $1 done" 
        } & 
    
        while true; do 
         # load the PIDs of all child processes to the array 
         joblist=(`jobs -p`) 
         if [ ${#joblist[*]} -ge "$CPU_NUMBER" ]; then 
          # when the job limit is reached, wait for *single* job to finish 
          wait -n 
         else 
          # stop checking when we're below the limit 
          break 
         fi 
        done 
        # it's great we executed zero external commands to check! 
    
        shift 
    done 
    
    # wait for all currently active child processes 
    wait 
    
    5

    Hier ist der kürzeste Weg:

    waitforjobs() { 
        while test $(jobs -p | wc -w) -ge "$1"; do wait -n; done 
    } 
    

    Rufen Sie diese Funktion, bevor sie eine neue Stelle abzweigt:

    waitforjobs 10 
    run_another_job & 
    

    Um so viele Hintergrundjobs als Kerne auf der Maschine zu haben, verwenden Sie $(nproc) anstelle einer festen Zahl wie 10.

    +0

    Awesome, aber für bash version> = 4 – user3769065

    +0

    'wait -n' ist nicht auf allen Systemen verfügbar ... – willsteel

    +0

    Dies hat eine Race-Bedingung - wenn einer der Jobs beendet ist, bevor Sie auf 'warten' kommen, dann Sie möglicherweise in einer Position, in der Sie einen anderen Job ausführen könnten, aber warten müssen, bis "warten" einen anderen Job fängt. –

    0

    Die folgende Funktion (entwickelt f rom Tangens beantworten oben, entweder kopieren in Skript oder Quelle aus der Datei):

    job_limit() { 
        # Test for single positive integer input 
        if (($# == 1)) && [[ $1 =~ ^[1-9][0-9]*$ ]] 
        then 
    
         # Check number of running jobs 
         joblist=($(jobs -rp)) 
         while ((${#joblist[*]} >= $1)) 
         do 
    
          # Wait for any job to finish 
          command='wait '${joblist[0]} 
          for job in ${joblist[@]:1} 
          do 
           command+=' || wait '$job 
          done 
          eval $command 
          joblist=($(jobs -rp)) 
         done 
        fi 
    } 
    

    1) Nur eine einzige Zeile erfordert das Einfügen einer vorhandenen Schleife zu begrenzen

    while : 
    do 
        task & 
        job_limit `nproc` 
    done 
    

    2) Wartet auf Abschluss bestehender Hintergrund Aufgaben statt Polling, Erhöhung der Effizienz für schnelle Aufgaben

    Verwandte Themen