代码之家  ›  专栏  ›  技术社区  ›  Betamoo

如何从bash脚本并行运行多个程序?

  •  182
  • Betamoo  · 技术社区  · 14 年前

    我想写一篇 SH文件 运行许多程序 同时

    我试过这个

    prog1 
    prog2
    

    但运行prog1,然后等待prog1结束,然后启动prog2…

    那么我如何并行运行它们呢?

    13 回复  |  直到 6 年前
        1
  •  164
  •   psmears Touffy    14 年前
    prog1 &
    prog2 &
    
        2
  •  243
  •   Ory Band    13 年前

    怎么样:

    prog1 & prog2 && fg
    

    这将:

    1. 起点 prog1 .
    2. 将其发送到后台,但继续打印其输出。
    3. 起点 prog2 把它放在前面 ,这样就可以用 ctrl-c .
    4. 当你靠近 PROF2 ,您将返回 PROF1 前景 ,这样您也可以用 Ctrl—C .
        3
  •  55
  •   Ole Tange    8 年前

    与GNU并联 http://www.gnu.org/software/parallel/ 它很容易做到:

    (echo prog1; echo prog2) | parallel
    

    或者如果你喜欢:

    parallel ::: prog1 prog2
    

    了解更多:

        4
  •  47
  •   trusktr    8 年前

    你可以使用 wait :

    some_command &
    P1=$!
    other_command &
    P2=$!
    wait $P1 $P2
    

    它将后台程序pids分配给变量( $! 是最后启动的进程的PID),然后 等待 命令等待它们。这很好,因为如果杀死脚本,它也会杀死进程!

        5
  •  9
  •   Community CDub    10 年前
    #!/bin/bash
    prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log
    

    将错误重定向到单独的日志。

        6
  •  8
  •   3h4x    14 年前

    有一个非常有用的程序调用nohup。

         nohup - run a command immune to hangups, with output to a non-tty
    
        7
  •  7
  •   arnaldocan    8 年前

    以下是我用来并行运行max n进程的函数(示例中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)..."
      "$@" && 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设置为核心数,此函数将尝试避免空闲核心。

        8
  •  7
  •   Quinn Comendant    6 年前

    如果您想使用 ctrl-c ,这是我最喜欢的方法:在 (…) 子壳和陷阱 SIGINT 执行 kill 0 将杀死子shell组中生成的所有内容:

    (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)
    
        9
  •  6
  •   ljt    14 年前

    你可以试试 ppss . PPS相当强大-您甚至可以创建一个小型集群。 如果您有一批令人尴尬的并行处理要做,xargs-p也会很有用。

        10
  •  6
  •   Joaopcribeiro    7 年前

    最近我遇到了类似的情况,我需要同时运行多个程序,将它们的输出重定向到单独的日志文件,然后等待它们完成,最后我得到如下结果:

    #!/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/

        11
  •  4
  •   mklement0    6 年前

    xargs -P <n> 允许你跑步 <n> 命令并行。

    同时 -P 是一个非标准选项,GNU(Linux)和MacOS/BSD实现都支持它。

    以下示例:

    • 至多 一次并行3个命令,
    • 只有在先前启动的进程终止时才启动其他命令。
    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 -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 参数代换 要求您指定并行运行的命令数 明确地 而GNU 参数代换 允许您指定 -P 0 尽可能多地跑 尽可能 并行地。

    • 并行运行的进程的输出到达 当它被生成时 所以它会 不可预知的交错 .

      • GNU parallel ,如中所述 Ole's answer (确实) 大多数平台都标配),方便 序列化 (分组)每个进程的输出,并提供更多高级功能。
        12
  •  2
  •   Josiah DeWitt    7 年前

    进程生成管理器

    当然,从技术上讲,这些都是进程,这个程序真的应该称为进程生成管理器,但这仅仅是因为bash使用与符号分叉时的工作方式,它使用fork()或clone()系统调用,将其克隆到单独的内存空间中,而不是像pthread_create()这样共享内存的东西。奥里。如果bash支持后者,那么每个“执行序列”的操作都将完全相同,可以称为传统线程,同时获得更有效的内存占用。但是,在功能上,它的工作原理是相同的,虽然有点困难,因为全局变量在每个工作克隆中都不可用,因此使用进程间通信文件和基本的群信号量来管理关键部分。当然,从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'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
    
        13
  •  -1
  •   Fil    6 年前

    巴什杰 ( 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