Ik probeer een .sh-bestandte schrijven dat veel programma’s gelijktijdig
uitvoert
Ik heb dit geprobeerd
prog1
prog2
Maar dat start prog1 en wacht dan tot prog1 eindigt en start dan prog2…
Dus hoe kan ik ze parallel laten lopen?
Antwoord 1, autoriteit 100%
Wat dacht je van:
prog1 & prog2 && fg
Dit zal:
- Start
prog1
. - Stuur het naar de achtergrond, maar blijf de uitvoer afdrukken.
- Start
prog2
en houd het op de voorgrond, zodat je het kunt sluiten metctrl-c
. - Als je
prog2
sluit, keer je terug naar de voorgrondvanprog1
, dus je kunt het ook sluiten metctrl-c
.
Antwoord 2, autoriteit 81%
Om meerdere programma’s parallel te laten lopen:
prog1 &
prog2 &
Als u wilt dat uw script wacht tot de programma’s klaar zijn, kunt u het volgende toevoegen:
wait
op het punt waar u wilt dat het script op hen wacht.
Antwoord 3, autoriteit 35%
Als je gemakkelijk meerdere processen wilt kunnen uitvoeren en doden met ctrl-c
, is dit mijn favoriete methode: meerdere achtergrondprocessen spawnen in een (…)
subshell, en trap SIGINT
in om kill 0
uit te voeren, wat alles zal doden dat in de subshell-groep is voortgekomen:
(trap 'kill 0' SIGINT; prog1 & prog2 & prog3)
U kunt complexe structuren voor procesuitvoering hebben, en alles wordt afgesloten met een enkele ctrl-c
(zorg er wel voor dat het laatste proces op de voorgrond wordt uitgevoerd, dwz voeg geen &
na prog1.3
):
(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)
Antwoord 4, autoriteit 28%
U kunt wait
gebruiken:
some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2
Het wijst de PID’s van het achtergrondprogramma toe aan variabelen ($!
is de PID van het laatst gestarte proces), waarna de opdracht wait
erop wacht. Het is leuk, want als je het script doodt, worden ook de processen vernietigd!
Antwoord 5, autoriteit 21%
Met GNU Parallel http://www.gnu.org/software/parallel/het is zo eenvoudig als:
(echo prog1; echo prog2) | parallel
Of als je wilt:
parallel ::: prog1 prog2
Meer informatie:
- Bekijk de introductievideo voor een korte introductie:
https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 - Loop door de tutorial (man parallel_tutorial). Uw opdrachtregel
zal er van je houden. - Lees: Ole Tange, GNU Parallel 2018(Ole Tange, 2018).
Antwoord 6, autoriteit 2%
Je kunt ppssproberen (verlaten). ppss is vrij krachtig – je kunt zelfs een minicluster maken.
xargs -P kan ook handig zijn als je een reeks beschamende parallelle verwerkingen te doen hebt.
Antwoord 7
Processpawning-manager
Natuurlijk, technisch gezien zijn dit processen, en dit programma zou eigenlijk een process spawning manager moeten worden genoemd, maar dit komt alleen door de manier waarop BASH werkt wanneer het forks gebruikt met het ampersand, het gebruikt de fork() of misschien clone( ) systeemaanroep die naar een aparte geheugenruimte kloont, in plaats van iets als pthread_create() dat geheugen zou delen. Als BASH dit laatste zou ondersteunen, zou elke “uitvoeringsvolgorde” precies hetzelfde werken en zou het traditionele threads kunnen worden genoemd, terwijl het een efficiëntere geheugenvoetafdruk zou krijgen. Functioneel werkt het echter hetzelfde, hoewel een beetje moeilijker omdat GLOBAL-variabelen niet beschikbaar zijn in elke werkkloon, vandaar het gebruik van het communicatiebestand tussen processen en de rudimentaire kudde-semafoor om kritieke secties te beheren. Forking van BASH is natuurlijk het basisantwoord hier, maar ik heb het gevoel dat mensen dat weten, maar echt willen beheren wat er wordt voortgebracht in plaats van het gewoon te forken en het te vergeten. Dit demonstreert een manier om tot 200 instanties van gevorkte processen te beheren die allemaal toegang hebben tot één enkele bron. Dit is duidelijk overdreven, maar ik vond het leuk om het te schrijven, dus ik ging door. Vergroot de grootte van uw terminal dienovereenkomstig. Ik hoop dat je dit nuttig vindt.
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