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

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

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

for i in {1..1000}
do
   foo i
done

我用参数调用函数foo1000 次i

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

所以如果我有

for i in {1..1000}
do
   foo i &
done

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

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

答案1

zsh而不是bash

autoload -Uz zargs
zargs -P100 -I{} -- {1..1000} -- foo {}

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

xargs -I{} -P100 -a <(echo {1..1000}) foo {}

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
)

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

答案2

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

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

结果显示时间重叠:

$ ./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
$ 

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

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

    sleep 2 && printf 'Foo %d\n' $1

命令:

$ 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
$ 

答案3

这是一个简单的方法,可以将它们分成 100 个块,(bash)

for i in {1..1000}
do
   foo "$i" &
   (( i % 100 )) || wait
done
wait

它假设在同一个 shell 中没有运行其他后台任务,只是启动 100 个任务,然后等待它们全部完成,然后再启动另外 100 个任务,最后等待任何剩余任务完成。 (1000和100没有余数,但在其他情况下可能有)

如果你的循环变量不是数字,你可以在表达式中使用++n而不是i

n=0
for i in *
do
   foo "$i" &
   (( ++n % 100 )) || wait
done
wait

答案4

GNU Parallel 正是针对这种情况而构建的:

parallel foo ::: {1..1000}

这将为foo n每个 CPU 线程运行一个(其中 n = 1..1000),直到所有 1000 个作业都运行完毕。当一项工作完成后,另一项工作就会开始。

parallel -j100 foo ::: {1..1000}

这将运行 100 个foo n(其中 n = 1..1000),直到所有 1000 个作业都运行完毕。

GNU Parallel会将输出序列化,因此如果两个foos同时打印,输出不会出现乱码。

GNU Parallel 还有许多其他功能可以使并行化变得更容易:花 20 分钟阅读第 1+2 章https://zenodo.org/record/1146014你的命令行会感谢你的。

相关内容