在 bash 脚本中一次启动 100 个进程

urn*_*god 7 shell-script parallelism

在 bash 脚本中,我有一个这样的程序

for i in {1..1000}
do
   foo i
done
Run Code Online (Sandbox Code Playgroud)

我用参数调用函数foo1000 次i

如果我想让它在多进程中运行,但不是同时运行,我该怎么办?

所以如果我有

for i in {1..1000}
do
   foo i &
done
Run Code Online (Sandbox Code Playgroud)

它会同时启动所有 1000 个进程,这不是我想要的。

有没有办法确保始终有 100 个进程在运行?如果某些过程完成,则开始一些新的过程,直到所有 1000 次迭代完成。或者,我可以等到所有 100 个都完成后,再运行另外 100 个。

Sté*_*las 15

zsh而不是bash

autoload -Uz zargs
zargs -P100 -I{} -- {1..1000} -- foo {}
Run Code Online (Sandbox Code Playgroud)

但如果你有 GNU xargs,你也可以这样做(在zsh,ksh93bash):

xargs -I{} -P100 -a <(echo {1..1000}) foo {}
Run Code Online (Sandbox Code Playgroud)

foo但必须是一个独立的命令。它不能与 shell 函数或内置函数一起使用。

请注意,zsh' 会zargs一个接一个地运行:启动 100 个作业,等待所有作业返回,然后才启动下一批 100 个作业。而 GNUxargs则会尝试保持最多 100 个作业运行:启动 100 个作业,然后启动另一个作业每次完成一个。

为了获得这种xargs行为,在 zsh 中,您可以在 SIGCHLD 中启动和管理作业池trap,每当后台进程返回时就会触发该作业池:

(
  todo=( {1..1000} ) max=100

  TRAPCHLD() {
    while (( $#jobstates < max && $#todo )); do
      foo $todo[1] & shift 1 todo
    done
  }

  : start &
  while (( $#todo )) wait
)
Run Code Online (Sandbox Code Playgroud)

在这里,我们需要在子 shell 中运行它来获取新的作业列表。SIGCHLD 在 TRAPCHLD 陷阱运行时被阻止,因此陷阱不应重新进入自身,这应避免竞争条件或需要防止并发访问列表$todo


Pau*_*ant 5

如果您可以分组运行,请嵌套一个循环:

#! /bin/bash

date '+%T.%N'
for j in {1..3}; do
    for k in {1..3}; do
        (( ++i ))
        ( sleep 2.0 && printf 'Foo %d\n' $i ) &
    done
    wait
    date '+%T.%N'
    printf 'Batch %d ends\n' $j 
done
date '+%T.%N'
Run Code Online (Sandbox Code Playgroud)

结果显示时间重叠:

$ ./aBatch
19:55:17.078476713
Foo 1
Foo 2
Foo 3
19:55:19.094302514
Batch 1 ends
Foo 4
Foo 6
Foo 5
19:55:21.114530543
Batch 2 ends
Foo 7
Foo 9
Foo 8
19:55:23.132184671
Batch 3 ends
19:55:23.135792952
$ 
Run Code Online (Sandbox Code Playgroud)

这在 GNU 并行中也是一样的。这样做的优点是,如果执行运行的时间不同,parallel将启动进一步的进程,而无需等待批处理中的其他进程。

#! /bin/bash
#.. The script ./aFoo

    sleep 2 && printf 'Foo %d\n' $1
Run Code Online (Sandbox Code Playgroud)

命令:

$ date '+%T.%N'; parallel -j 3 ./aFoo -- {1..9}; date '+%T.%N'
20:11:44.446042653
Foo 3
Foo 1
Foo 2
Foo 4
Foo 5
Foo 6
Foo 7
Foo 8
Foo 9
20:11:50.503324162
$ 
Run Code Online (Sandbox Code Playgroud)


Min*_*Max 2

#!/bin/bash

jobs_to_run_num=10
simult_jobs_num=3
have_runned_jobs_cntr=0
check_interval=0.1

while ((have_runned_jobs_cntr < jobs_to_run_num)); do 
    cur_jobs_num=$(wc -l < <(jobs -r))

    if ((cur_jobs_num < simult_jobs_num)); then
        ./random_time_script.sh &
        echo -e "cur_jobs_num\t$((cur_jobs_num + 1))"
        ((have_runned_jobs_cntr++))
    # sleep is needed to reduce the frequency of while loop
    # otherwise it itself will eat a lot of processor time
    # by restlessly checking
    else
        sleep "$check_interval"
    fi  
done
Run Code Online (Sandbox Code Playgroud)

更好的方法- 通过使用wait -n. 无需在每次迭代和使用sleep命令时检查作业编号。

jobs_to_run_num=10
simult_jobs_num=3

while ((have_runned_jobs_cntr < jobs_to_run_num)); do
    if (( i++ >= simult_jobs_num )); then
        wait -n   # wait for any job to complete. New in 4.3
    fi
    ./random_time_script.sh &
    ((have_runned_jobs_cntr++))

    # For demonstration
    cur_jobs_num=$(wc -l < <(jobs -r))
    echo -e "cur_jobs_num\t${cur_jobs_num}"
done 
Run Code Online (Sandbox Code Playgroud)

这里的想法 -我想并行处理一堆文件,当一个文件完成时,我想开始下一个。我想确保同时运行 5 个作业。

测试

$ ./test_simult_jobs.sh 
cur_jobs_num    1
cur_jobs_num    2
cur_jobs_num    3
cur_jobs_num    3
cur_jobs_num    3
cur_jobs_num    3
cur_jobs_num    3
cur_jobs_num    3
cur_jobs_num    3
cur_jobs_num    3
Run Code Online (Sandbox Code Playgroud)