Я пытаюсь написать файл .sh, который запускает много программ одновременно
Я пробовал это
prog1
prog2
Но это запускает prog1, затем ждет, пока prog1 не закончит, а затем запустит prog2...
Итак, как я могу запускать их параллельно?
Я пытаюсь написать файл .sh, который запускает много программ одновременно
Я пробовал это
prog1
prog2
Но это запускает prog1, затем ждет, пока prog1 не закончит, а затем запустит prog2...
Итак, как я могу запускать их параллельно?
prog1 &
prog2 &
Как насчет:
prog1 & prog2 && fg
Это будет:
prog1
.prog2
и сохраните его на переднем плане, чтобы вы могли закрыть его с помощью ctrl-c
.prog2
, вы вернетесь к prog1
переднего плана, так что вы также можете закрыть его с помощью ctrl-c
.С GNU Parallel http://www.gnu.org/software/parallel/ это так же просто, как:
(echo prog1; echo prog2) | parallel
Или, если вы предпочитаете:
parallel ::: prog1 prog2
Подробнее...
Вы можете использовать wait
:
some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2
Он присваивает переменные PID исходной программы переменным ($!
является последним запущенным процессом 'PID), тогда команда wait
ждет их. Это хорошо, потому что, если вы убиваете script, он также убивает процессы!
Если вы хотите иметь возможность легко запускать и уничтожать несколько процессов с помощью ctrl-c
, это мой любимый метод: порождать несколько фоновых процессов в подоболочке (…)
и перехватывать SIGINT
для выполнения kill 0
, что уничтожит все порождается в группе subshell:
(trap 'kill 0' SIGINT; prog1 & prog2 & prog3)
У вас могут быть сложные структуры выполнения процесса, и все будет закрываться с помощью одного ctrl-c
(просто убедитесь, что последний процесс запущен на переднем плане, т.е. не включайте &
после prog1.3
):
(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
Перенаправить ошибки в отдельные журналы.
Существует очень полезная программа, которая вызывает nohup.
nohup - run a command immune to hangups, with output to a non-tty
xargs -P <n>
позволяет запускать команды <n>
параллельно.
Хотя -P
является нестандартным вариантом, его поддерживают как реализации GNU (Linux), так и macOS/BSD.
Следующий пример:
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
Вывод выглядит примерно так:
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
Время показывает, что команды были запущены параллельно (последняя команда была запущена только после того, как первая из исходных 3 завершилась, но была выполнена очень быстро).
Сама команда xargs
не вернется, пока все команды не будут завершены, но вы можете выполнить ее в фоновом режиме, завершив ее оператором управления &
, а затем используя встроенную функцию wait
для ожидания всей команды xargs
закончить.
{
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 $!
Примечание:
BSD/macOS xargs
требует, чтобы вы указали количество команд для явного параллельного запуска, тогда как GNU xargs
позволяет вам указать -P 0
для параллельного запуска максимально возможного числа.
Выходные данные параллельных процессов поступают по мере их генерации, поэтому они будут непредсказуемо чередоваться.
parallel
, как упомянуто в Ole answer (не входит в стандартную комплектацию большинства платформ), удобно сериализует (группирует) вывод для каждого процесса и предлагает множество дополнительных функций.Вы можете попробовать ppss. ppss довольно мощный - вы даже можете создать мини-кластер. xargs -P также может быть полезна, если вам нужна партия неловкой параллельной обработки.
Недавно у меня была похожая ситуация, когда мне нужно было одновременно запускать несколько программ, перенаправлять их выходы на отдельные файлы журналов и ждать их завершения, и я закончил с чем-то вроде этого:
#!/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
Источник: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/
Вот функция, которую я использую для параллельного параллельного параллельного процесса (n = 4 в примере):
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
Если max_children установлен на количество ядер, эта функция будет пытаться избежать простоя.
Диспетчер порождения процессов
Конечно, технически это процессы, и эта программа должна называться диспетчером порождения процессов, но это только из-за того, что BASH работает, когда он разветвляется, используя амперсанд, он использует системный вызов fork() или, возможно, clone(). который клонируется в отдельное пространство памяти, а не что-то вроде pthread_create(), который бы разделял память. Если бы BASH поддерживал последнее, каждая "последовательность выполнения" работала бы одинаково и могла бы называться традиционными потоками, в то же время получая более эффективный объем памяти. Функционально, однако, он работает так же, хотя и немного сложнее, поскольку переменные GLOBAL недоступны в каждом рабочем клоне, поэтому для управления критическими секциями используется межпроцессный коммуникационный файл и элементарный семафор стада. Формирование от BASH, конечно, является основным ответом здесь, но я чувствую, как будто люди знают это, но действительно хотят управлять тем, что порождено, а не просто раскошелиться и забыть это. Это демонстрирует способ управления до 200 экземплярами разветвленных процессов, каждый из которых обращается к одному ресурсу. Ясно, что это излишне, но мне понравилось писать, поэтому я продолжил. Увеличьте размер вашего терминала соответственно. Надеюсь, вы найдете это полезным.
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 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
С bashj (https://sourceforge.net/projects/bashj/) вы сможете запускать не только несколько процессов (как предлагали другие), но также несколько потоков в одной JVM, управляемой из вашего скрипта. Но, конечно, это требует Java JDK. Потоки потребляют меньше ресурсов, чем процессы.
Вот рабочий код:
#!/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