Ausführen von Befehlen parallel mit einer begrenzten Anzahl von Befehlen gleichzeitig

23

Sequenziell: for i in {1..1000}; do do_something $i; done- zu langsam

Parallel: for i in {1..1000}; do do_something $i& done- zu viel Last

Wie werden Befehle parallel ausgeführt, jedoch nicht mehr als beispielsweise 20 Instanzen pro Moment?

Heutzutage wird normalerweise ein Hack verwendet for i in {1..1000}; do do_something $i& sleep 5; done, aber dies ist keine gute Lösung.

Update 2 : Konvertierte die akzeptierte Antwort in ein Skript: http://vi-server.org/vi/parallel

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Beachten Sie, dass Sie 8 Leerzeichen durch 2 Tabulatoren vor "i =" ersetzen müssen, damit es funktioniert.

Vi.
quelle

Antworten:

15

GNU Parallel ist dafür gemacht.

seq 1 1000 | parallel -j20 do_something

Es können sogar Jobs auf Remote-Computern ausgeführt werden. Hier ist ein Beispiel für das Umcodieren einer MP3 in OGG unter Verwendung von Server2 und einem lokalen Computer, auf dem 1 Auftrag pro CPU-Kern ausgeführt wird:

parallel --trc {.}.ogg -j+0 -S server2,: \
     'mpg321 -w - {} | oggenc -q0 - -o {.}.ogg' ::: *.mp3

Sehen Sie sich hier ein Einführungsvideo zu GNU Parallel an:

http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
quelle
Ich habe keine Ahnung von "moreutils" und dass es bereits ein Tool für diesen Job gibt. Anschauen und vergleichen.
Vi.
1
Das parallelin moreutils ist nicht GNU Parallel und ist in seinen Optionen ziemlich eingeschränkt. Der Befehl oben wird nicht mit der Parallele von moreutils ausgeführt.
Ole Tange
1
Eine weitere Option: xargs --max-procs=20.
Vi.
4

Keine Bash-Lösung, aber Sie sollten ein Makefile verwenden, das möglicherweise -ldie maximale Last nicht überschreitet.

NJOBS=1000

.PHONY = jobs
jobs = $(shell echo {1..$(NJOBS)})

all: $(jobs)

$(jobs):
    do_something $@

Dann starten Sie 20 Jobs gleichzeitig

$ make -j20

oder um so viele Jobs wie möglich zu starten, ohne eine Last von 5 zu überschreiten

$ make -j -l5
Benjamin Bannier
quelle
Sieht wie die nicht-hackige Lösung fürs Erste aus.
Vi.
2
echo -e 'PHONY=jobs\njobs=$(shell echo {1..100000})\n\nall: ${jobs}\n\n${jobs}:\n\t\techo $@; sleep `echo $$RANDOM/6553 | bc -l`' | make -f - -j20Jetzt sieht es wieder hackiger aus.
Vi.
@vi: oh mein ....
Benjamin Bannier
Konvertierte Ihre Lösung in ein Skript. Jetzt kann es problemlos verwendet werden.
Vi.
2

Posten des Skripts in der Frage mit Formatierung:

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Beachten Sie, dass Sie vor "i =" 8 Leerzeichen durch 2 Tabulatoren ersetzen müssen.

Warren
quelle
1

Eine einfache Idee:

Prüfe auf i modulo 20 und führe den wait shell-Befehl aus, bevor du etwas tust.

Harrymc
quelle
Es wird entweder darauf warten, dass alle aktuellen Aufgaben abgeschlossen sind (Erstellen von Nachteilen bei der Anzahl der Aufgaben) oder auf eine bestimmte Aufgabe warten, die längere Zeit blockieren kann (erneutes Erstellen von Nachteilen in diesem Fall)
Vi.
@Vi: Die Shell wartet auf alle Hintergrundaufgaben, die zu dieser Shell gehören.
Harrymc
1

Mit psdieser Option können Sie zählen, wie viele Prozesse ausgeführt werden. Wenn diese einen bestimmten Schwellenwert unterschreiten, starten Sie einen anderen Prozess.

Pseudocode:

i = 1
MAX_PROCESSES=20
NUM_TASKS=1000
do
  get num_processes using ps
  if num_processes < MAX_PROCESSES
    start process $i
    $i = $i + 1
  endif
  sleep 1 # add this to prevent thrashing with ps
until $i > NUM_TASKS
Paul R
quelle
1
for i in {1..1000}; do 
     (echo $i ; sleep `expr $RANDOM % 5` ) &
     while [ `jobs | wc -l` -ge 20 ] ; do 
         sleep 1 
     done
done
msw
quelle
Kann sein while [ `jobs | wc -l` -ge 20]; do?
Vi.
sicher, aber in meinem Beispiel müsste ich dann njobszweimal rechnen , und die Leistung ist in Shell-Skripten, die Sleep-Tasks ausführen, ziemlich wichtig;)
msw
Ich meine, deine Version funktioniert nicht wie erwartet. Ich wechsle sleep 1zu sleep 0.1und es beginnt mit einem durchschnittlichen Job von 40-50 statt 20. Wenn es mehr als 20 Jobs gibt, müssen wir warten, bis ein Job fertig ist, und nicht nur 1 Sekunde warten.
Vi.
0

du kannst es so machen.

threads=20
tempfifo=$PMS_HOME/$$.fifo

trap "exec 1000>&-;exec 1000<&-;exit 0" 2
mkfifo $tempfifo
exec 1000<>$tempfifo
rm -rf $tempfifo

for ((i=1; i<=$threads; i++))
do
    echo >&1000
done

for ((j=1; j<=1000; j++))
do
    read -u1000
    {
        echo $j
        echo >&1000
    } &
done

wait
echo "done!!!!!!!!!!"

Jedes Mal, wenn Named Pipes verwendet werden, werden 20 Subshells parallel ausgeführt.

Hoffe es hilft :)

ouyangyewei
quelle