Ich versuche, eine .sh-Datei zu schreiben, die viele Programme gleichzeitig ausführt
Ich habe es versucht
prog1
prog2
Aber das führt prog1 aus und wartet dann, bis prog1 endet und dann prog2 startet ...
Wie kann ich sie also parallel ausführen?
prog1 &
prog2 &
Wie wäre es mit:
prog1 & prog2 && fg
Dieser Wille:
prog1
.prog2
Und lassen Sie es im Vordergrund, damit Sie es mit ctrl-c
Schließen können.prog2
Schließen, kehren Sie zum Vordergrund von prog1
Zurück, sodass Sie es auch mit ctrl-c
Schließen können.Mit GNU Parallel http://www.gnu.org/software/parallel/ ist es so einfach wie:
(echo prog1; echo prog2) | parallel
Oder wenn Sie es vorziehen:
parallel ::: prog1 prog2
Erfahren Sie mehr:
Sie können wait
verwenden:
some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2
Es ordnet die PIDs des Hintergrundprogramms Variablen zu ($!
ist der zuletzt gestartete Prozess (PID), dann wartet der Befehl wait
auf sie. Es ist schön, denn wenn Sie das Skript beenden, werden auch die Prozesse beendet!
Wenn Sie in der Lage sein möchten, mehrere Prozesse einfach mit ctrl-c
Auszuführen und abzubrechen, ist dies meine Lieblingsmethode: Mehrere Hintergrundprozesse in einer (…)
- Subshell erzeugen und SIGINT
abfangen führe kill 0
aus, was alles, was in der Subshell-Gruppe auftaucht, tötet:
(trap 'kill 0' SIGINT; prog1 & prog2 & prog3)
Sie können komplexe Prozessausführungsstrukturen haben und alles wird mit einem einzelnen ctrl-c
Geschlossen (stellen Sie nur sicher, dass der letzte Prozess im Vordergrund ausgeführt wird, dh fügen Sie nach &
Kein prog1.3
Ein = CODE!]):
(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log
Leiten Sie Fehler in separate Protokolle um.
Es gibt ein sehr nützliches Programm, das Nohup aufruft.
Nohup - run a command immune to hangups, with output to a non-tty
Hier ist eine Funktion, die ich benutze, um mit maximal n Prozessen parallel zu laufen (n = 4 im Beispiel):
max_children=4
function parallel {
local time1=$(date +"%H:%M:%S")
local time2=""
# for the sake of the example, I'm using $2 as a description, you may be interested in other description
echo "starting $2 ($time1)..."
"[email protected]" && time2=$(date +"%H:%M:%S") && echo "finishing $2 ($time1 -- $time2)..." &
local my_pid=$$
local children=$(ps -eo ppid | grep -w $my_pid | wc -w)
children=$((children-1))
if [[ $children -ge $max_children ]]; then
wait -n
fi
}
parallel sleep 5
parallel sleep 6
parallel sleep 7
parallel sleep 8
parallel sleep 9
wait
Wenn max_children auf die Anzahl der Kerne eingestellt ist, versucht diese Funktion, Leerlaufkerne zu vermeiden.
Sie können versuchen ppss . ppss ist ziemlich leistungsfähig - Sie können sogar einen Mini-Cluster erstellen. xargs -P kann auch nützlich sein, wenn Sie eine Menge peinlich paralleler Verarbeitung durchführen müssen.
Mit xargs -P <n>
Können Sie <n>
- Befehle parallel ausführen.
Während -P
Keine Standardoption ist, wird sie sowohl von der GNU (Linux) - als auch von der macOS/BSD-Implementierung unterstützt.
Das folgende Beispiel:
time xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF
Die Ausgabe sieht ungefähr so aus:
1 # output from 1st command
4 # output from *last* command, which started as soon as the count dropped below 3
2 # output from 2nd command
3 # output from 3rd command
real 0m3.012s
user 0m0.011s
sys 0m0.008s
Das Timing zeigt, dass die Befehle parallel ausgeführt wurden (der letzte Befehl wurde erst gestartet, nachdem der erste der ursprünglichen 3 beendet wurde, aber sehr schnell ausgeführt wurde).
Der Befehl xargs
selbst wird nicht zurückgegeben, bis alle Befehle beendet sind. Sie können ihn jedoch im Hintergrund ausführen, indem Sie ihn mit dem Steueroperator &
Beenden und dann die integrierte Funktion wait
verwenden warten, bis der gesamte Befehl xargs
beendet ist.
{
xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF
} &
# Script execution continues here while `xargs` is running
# in the background.
echo "Waiting for commands to finish..."
# Wait for `xargs` to finish, via special variable $!, which contains
# the PID of the most recently started background process.
wait $!
Hinweis:
In BSD/macOS xargs
müssen Sie die Anzahl der Befehle angeben, die parallel ausgeführt werden sollen explizit, während Sie GNU xargs
angeben können -P 0
, Um möglichst viele parallel auszuführen.
Die Ausgabe der Prozesse, die parallel ausgeführt werden, kommt an während sie generiert wird, daher wird sie unvorhersehbar verschachtelt.
parallel
, wie in Ole's Antwort (kommt nicht standardmäßig mit den meisten Plattformen), bequem serialisiert (gruppiert) die Ausgabe pro Prozess und bietet viele erweiterte Funktionen.Vor kurzem hatte ich eine ähnliche Situation, in der ich mehrere Programme gleichzeitig ausführen, ihre Ausgaben in getrennte Protokolldateien umleiten und auf deren Abschluss warten musste. Am Ende hatte ich Folgendes:
#!/bin/bash
# Add the full path processes to run to the array
PROCESSES_TO_RUN=("/home/joao/Code/test/prog_1/prog1" \
"/home/joao/Code/test/prog_2/prog2")
# You can keep adding processes to the array...
for i in ${PROCESSES_TO_RUN[@]}; do
${i%/*}/./${i##*/} > ${i}.log 2>&1 &
# ${i%/*} -> Get folder name until the /
# ${i##*/} -> Get the filename after the /
done
# Wait for the processes to finish
wait
Quelle: http://joaoperibeiro.com/de/execute-multiple-programs-and-redirect-their-outputs-linux/
Process Spawning Manager
Sicher, technisch gesehen handelt es sich um Prozesse, und dieses Programm sollte eigentlich als Prozess-Laich-Manager bezeichnet werden. Dies liegt jedoch nur an der Funktionsweise von BASH, wenn es nach dem kaufmännischen Und-Zeichen fragt und den Systemaufruf fork () oder vielleicht clone () verwendet Dies klont in einen separaten Speicherbereich anstatt in etwas wie pthread_create (), das sich den Speicher teilt. Wenn BASH das letztere unterstützt, würde jede "Ausführungssequenz" genauso ablaufen und könnte als traditioneller Thread bezeichnet werden, während ein effizienterer Speicherbedarf erzielt wird. Funktionell funktioniert es jedoch genauso, obwohl es etwas schwieriger ist, da GLOBAL-Variablen nicht in jedem Worker-Klon verfügbar sind, weshalb die prozessübergreifende Kommunikationsdatei und das rudimentäre Flock-Semaphor zum Verwalten kritischer Abschnitte verwendet werden. Das Gabeln von BASH ist natürlich die grundlegende Antwort, aber ich habe das Gefühl, als ob die Leute das wissen, aber wirklich versuchen, das zu verwalten, was erzeugt wird, anstatt es einfach abzweigen und es zu vergessen. Dies zeigt eine Möglichkeit zum Verwalten von bis zu 200 Instanzen gegabelter Prozesse, die alle auf eine einzelne Ressource zugreifen. Klar, das ist übertrieben, aber ich habe es genossen, es zu schreiben, also habe ich weitergemacht. Erhöhen Sie die Größe Ihres Terminals entsprechend. Ich hoffe, Sie finden das nützlich.
ME=$(basename $0)
IPC="/tmp/$ME.ipc" #interprocess communication file (global thread accounting stats)
DBG=/tmp/$ME.log
echo 0 > $IPC #initalize counter
F1=thread
SPAWNED=0
COMPLETE=0
SPAWN=1000 #number of jobs to process
SPEEDFACTOR=1 #dynamically compensates for execution time
THREADLIMIT=50 #maximum concurrent threads
TPS=1 #threads per second delay
THREADCOUNT=0 #number of running threads
SCALE="scale=5" #controls bc's precision
START=$(date +%s) #whence we began
MAXTHREADDUR=6 #maximum thread life span - demo mode
LOWER=$[$THREADLIMIT*100*90/10000] #90% worker utilization threshold
UPPER=$[$THREADLIMIT*100*95/10000] #95% worker utilization threshold
DELTA=10 #initial percent speed change
threadspeed() #dynamically adjust spawn rate based on worker utilization
{
#vaguely assumes thread execution average will be consistent
THREADCOUNT=$(threadcount)
if [ $THREADCOUNT -ge $LOWER ] && [ $THREADCOUNT -le $UPPER ] ;then
echo SPEED HOLD >> $DBG
return
Elif [ $THREADCOUNT -lt $LOWER ] ;then
#if maxthread is free speed up
SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1-($DELTA/100))"|bc)
echo SPEED UP $DELTA%>> $DBG
Elif [ $THREADCOUNT -gt $UPPER ];then
#if maxthread is active then slow down
SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1+($DELTA/100))"|bc)
DELTA=1 #begin fine grain control
echo SLOW DOWN $DELTA%>> $DBG
fi
echo SPEEDFACTOR $SPEEDFACTOR >> $DBG
#average thread duration (total elapsed time / number of threads completed)
#if threads completed is zero (less than 100), default to maxdelay/2 maxthreads
COMPLETE=$(cat $IPC)
if [ -z $COMPLETE ];then
echo BAD IPC READ ============================================== >> $DBG
return
fi
#echo Threads COMPLETE $COMPLETE >> $DBG
if [ $COMPLETE -lt 100 ];then
AVGTHREAD=$(echo "$SCALE;$MAXTHREADDUR/2"|bc)
else
ELAPSED=$[$(date +%s)-$START]
#echo Elapsed Time $ELAPSED >> $DBG
AVGTHREAD=$(echo "$SCALE;$ELAPSED/$COMPLETE*$THREADLIMIT"|bc)
fi
echo AVGTHREAD Duration is $AVGTHREAD >> $DBG
#calculate timing to achieve spawning each workers fast enough
# to utilize threadlimit - average time it takes to complete one thread / max number of threads
TPS=$(echo "$SCALE;($AVGTHREAD/$THREADLIMIT)*$SPEEDFACTOR"|bc)
#TPS=$(echo "$SCALE;$AVGTHREAD/$THREADLIMIT"|bc) # maintains pretty good
#echo TPS $TPS >> $DBG
}
function plot()
{
echo -en \\033[${2}\;${1}H
if [ -n "$3" ];then
if [[ $4 = "good" ]];then
echo -en "\\033[1;32m"
Elif [[ $4 = "warn" ]];then
echo -en "\\033[1;33m"
Elif [[ $4 = "fail" ]];then
echo -en "\\033[1;31m"
Elif [[ $4 = "crit" ]];then
echo -en "\\033[1;31;4m"
fi
fi
echo -n "$3"
echo -en "\\033[0;39m"
}
trackthread() #displays thread status
{
WORKERID=$1
THREADID=$2
ACTION=$3 #setactive | setfree | update
AGE=$4
TS=$(date +%s)
COL=$[(($WORKERID-1)/50)*40]
ROW=$[(($WORKERID-1)%50)+1]
case $ACTION in
"setactive" )
touch /tmp/$ME.$F1$WORKERID #redundant - see main loop
#echo created file $ME.$F1$WORKERID >> $DBG
plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID INIT " good
;;
"update" )
plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID AGE:$AGE" warn
;;
"setfree" )
plot $COL $ROW "Worker$WORKERID: FREE " fail
rm /tmp/$ME.$F1$WORKERID
;;
* )
;;
esac
}
getfreeworkerid()
{
for i in $(seq 1 $[$THREADLIMIT+1])
do
if [ ! -e /tmp/$ME.$F1$i ];then
#echo "getfreeworkerid returned $i" >> $DBG
break
fi
done
if [ $i -eq $[$THREADLIMIT+1] ];then
#echo "no free threads" >> $DBG
echo 0
#exit
else
echo $i
fi
}
updateIPC()
{
COMPLETE=$(cat $IPC) #read IPC
COMPLETE=$[$COMPLETE+1] #increment IPC
echo $COMPLETE > $IPC #write back to IPC
}
worker()
{
WORKERID=$1
THREADID=$2
#echo "new worker WORKERID:$WORKERID THREADID:$THREADID" >> $DBG
#accessing common terminal requires critical blocking section
(flock -x -w 10 201
trackthread $WORKERID $THREADID setactive
)201>/tmp/$ME.lock
let "RND = $RANDOM % $MAXTHREADDUR +1"
for s in $(seq 1 $RND) #simulate random lifespan
do
sleep 1;
(flock -x -w 10 201
trackthread $WORKERID $THREADID update $s
)201>/tmp/$ME.lock
done
(flock -x -w 10 201
trackthread $WORKERID $THREADID setfree
)201>/tmp/$ME.lock
(flock -x -w 10 201
updateIPC
)201>/tmp/$ME.lock
}
threadcount()
{
TC=$(ls /tmp/$ME.$F1* 2> /dev/null | wc -l)
#echo threadcount is $TC >> $DBG
THREADCOUNT=$TC
echo $TC
}
status()
{
#summary status line
COMPLETE=$(cat $IPC)
plot 1 $[$THREADLIMIT+2] "WORKERS $(threadcount)/$THREADLIMIT SPAWNED $SPAWNED/$SPAWN COMPLETE $COMPLETE/$SPAWN SF=$SPEEDFACTOR TIMING=$TPS"
echo -en '\033[K' #clear to end of line
}
function main()
{
while [ $SPAWNED -lt $SPAWN ]
do
while [ $(threadcount) -lt $THREADLIMIT ] && [ $SPAWNED -lt $SPAWN ]
do
WID=$(getfreeworkerid)
worker $WID $SPAWNED &
touch /tmp/$ME.$F1$WID #if this loops faster than file creation in the worker thread it steps on itself, thread tracking is best in main loop
SPAWNED=$[$SPAWNED+1]
(flock -x -w 10 201
status
)201>/tmp/$ME.lock
sleep $TPS
if ((! $[$SPAWNED%100]));then
#rethink thread timing every 100 threads
threadspeed
fi
done
sleep $TPS
done
while [ "$(threadcount)" -gt 0 ]
do
(flock -x -w 10 201
status
)201>/tmp/$ME.lock
sleep 1;
done
status
}
clear
threadspeed
main
wait
status
echo