Odpowiedzi:
Co powiesz na:
prog1 & prog2 && fg
Spowoduje to:
prog1
.prog2
i trzymaj go na pierwszym planie , abyś mógł go zamknąć ctrl-c
.prog2
, można powrócić do prog1
„s planie , dzięki czemu można również zamknąć go z ctrl-c
.prog1
, gdy prog2
zakończone jest? Pomyśl o node srv.js & cucumberjs
prog1 & prog2 ; fg
służyła do uruchamiania wielu tuneli ssh jednocześnie. Mam nadzieję, że to komuś pomoże.
prog2
nie uruchomi się natychmiast, wrócisz do posiadania prog1
na pierwszym planie. Jeśli jest to pożądane, to jest w porządku.
prog1 & prog2 && kill $!
.
Możesz użyć wait
:
some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2
Przypisuje PID programu w tle do zmiennych ( $!
jest to PID ostatniego uruchomionego procesu), a następnie wait
polecenie czeka na nie. To miłe, ponieważ jeśli zabijesz skrypt, to również zabije procesy!
#!/usr/bin/env bash ARRAY='cat bat rat' for ARR in $ARRAY do ./run_script1 $ARR & done P1=$! wait $P1 echo "INFO: Execution of all background processes in the for loop has completed.."
${}
aby interpolować go do listy ciągów lub podobnej.
Z GNU Parallel http://www.gnu.org/software/parallel/ jest to tak proste, jak:
(echo prog1; echo prog2) | parallel
Lub jeśli wolisz:
parallel ::: prog1 prog2
Ucz się więcej:
parallel
o różnej składni. Na przykład w pochodnych Debiana moreutils
pakiet zawiera inną komendę o nazwie, parallel
która zachowuje się zupełnie inaczej.
parallel
lepsze niż używanie &
?
parallel
jest lepszy, gdy jest więcej zadań niż rdzeni, w którym to przypadku &
można uruchomić więcej niż jedno zadanie na rdzeń jednocześnie. (por. zasada szuflady )
Jeśli chcesz mieć możliwość łatwego uruchamiania i zabijania wielu procesów ctrl-c
, jest to moja ulubiona metoda: spawnuj wiele procesów w tle w (…)
podpowłoce i pułapkę SIGINT
do wykonania kill 0
, która zabije wszystko odrodzone w grupie podpowłoki:
(trap 'kill 0' SIGINT; prog1 & prog2 & prog3)
Możesz mieć złożone struktury wykonawcze procesów, a wszystko zamknie się za pomocą jednego ctrl-c
(upewnij się, że ostatni proces jest uruchomiony na pierwszym planie, tzn. Nie dołączaj &
po nim prog1.3
):
(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)
xargs -P <n>
pozwala na uruchamianie <n>
poleceń równolegle.
Chociaż -P
jest to niestandardowa opcja, zarówno implementacje GNU (Linux), jak i macOS / BSD ją obsługują.
Poniższy przykład:
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
Dane wyjściowe wyglądają mniej więcej tak:
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
Czas pokazuje, że polecenia były uruchamiane równolegle (ostatnie polecenie zostało uruchomione dopiero po zakończeniu pierwszego z 3 oryginalnych, ale wykonane bardzo szybko).
Samo xargs
polecenie nie powróci, dopóki wszystkie polecenia nie zostaną zakończone, ale można je wykonać w tle, kończąc je operatorem sterowania, &
a następnie używając wait
wbudowanego programu, aby czekać na zakończenie całego xargs
polecenia.
{
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 $!
Uwaga:
BSD / MacOS xargs
wymaga, aby określić liczbę poleceń uruchomić równolegle wyraźnie , natomiast GNU xargs
pozwala określić -P 0
uruchomić jak najwięcej jak to możliwe równolegle.
Dane wyjściowe z procesów przebiegających równolegle docierają w miarę ich generowania , więc będą nieprzewidywalnie przeplatane .
parallel
, jak wspomniano w odpowiedzi Ole ( nie jest standardem w większości platform), wygodnie serializuje (grupuje) dane wyjściowe dla poszczególnych procesów i oferuje wiele bardziej zaawansowanych funkcji.#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log
Błędy przekierowujące do oddzielnych dzienników.
prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log
. Błędy trafiają do konsoli, a oba pliki błędów są puste. Jak mówi @Dennis Williamson, &
jest separatorem, ;
więc (a) musi przejść na końcu polecenia (po każdym przekierowaniu) i (b) wcale nie potrzebujesz ;
:-)
Istnieje bardzo przydatny program, który wywołuje nohup.
nohup - run a command immune to hangups, with output to a non-tty
nohup
sam w sobie nie uruchamia niczego w tle, a używanie nohup
nie jest wymaganiem ani warunkiem wstępnym do uruchamiania zadań w tle. Często są one przydatne razem, ale jako takie nie odpowiadają na pytanie.
Oto funkcja, której używam, aby uruchomić równolegle maksymalnie n procesu (n = 4 w przykładzie):
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)..."
"$@" && 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
Jeśli parametr max_children jest ustawiony na liczbę rdzeni, funkcja ta spróbuje uniknąć wolnych rdzeni.
wait -n
wymaga wersji bash
4.3+ i zmienia logikę na oczekiwanie na zakończenie dowolnego z podanych / domyślnych procesów.
Ostatnio miałem podobną sytuację, w której musiałem uruchamiać wiele programów jednocześnie, przekierowywać ich wyniki do oddzielnych plików dziennika i czekać na ich zakończenie, a skończyło się na czymś takim:
#!/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
Źródło: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/
Process Spawning Manager
Jasne, technicznie rzecz biorąc, są to procesy, a ten program powinien naprawdę nazywać się menedżerem spawnowania procesów, ale dzieje się tak tylko ze względu na sposób, w jaki BASH działa, gdy rozwidla się za pomocą ampersand, używa wywołania systemowego fork () lub clone () który klonuje do oddzielnej przestrzeni pamięci zamiast czegoś takiego jak pthread_create (), który współdzieli pamięć. Gdyby BASH wspierał to drugie, każda „sekwencja wykonania” działałaby tak samo i mogłaby być nazwana tradycyjnym wątkiem, zyskując bardziej wydajny ślad pamięci. Funkcjonalnie jednak działa tak samo, choć nieco trudniej, ponieważ zmienne GLOBALNE nie są dostępne w każdym klonie roboczym, stąd użycie pliku komunikacji międzyprocesowej i podstawowego semafora stad do zarządzania krytycznymi sekcjami. Rozwidlenie od BASH jest oczywiście podstawową odpowiedzią tutaj, ale czuję się tak, jakby ludzie to wiedzieli, ale naprawdę chcą zarządzać tym, co się pojawiło, zamiast tylko rozwidlać je i zapomnieć. Pokazuje to sposób zarządzania nawet 200 wystąpieniami rozwidlonych procesów, z których wszystkie uzyskują dostęp do jednego zasobu. Najwyraźniej to przesada, ale podobało mi się pisanie, więc kontynuowałem. Zwiększ odpowiednio rozmiar swojego terminala. Mam nadzieję, że uznasz to za przydatne.
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
Twój skrypt powinien wyglądać następująco:
prog1 &
prog2 &
.
.
progn &
wait
progn+1 &
progn+2 &
.
.
Zakładając, że twój system może przyjmować n zadań jednocześnie. użyj funkcji wait, aby uruchomić tylko n zadań jednocześnie.
Dzięki bashj ( https://sourceforge.net/projects/bashj/ ) powinieneś być w stanie uruchomić nie tylko wiele procesów (tak jak sugerowali inni), ale także wiele wątków w jednym JVM kontrolowanym ze skryptu. Ale oczywiście wymaga to JDK Javy. Wątki zużywają mniej zasobów niż procesy.
Oto działający kod:
#!/usr/bin/bashj
#!java
public static int cnt=0;
private static void loop() {u.p("java says cnt= "+(cnt++));u.sleep(1.0);}
public static void startThread()
{(new Thread(() -> {while (true) {loop();}})).start();}
#!bashj
j.startThread()
while [ j.cnt -lt 4 ]
do
echo "bash views cnt=" j.cnt
sleep 0.5
done
wait
! Tak, w bash możesz poczekać na procesy potomne skryptu.