urn*_*god 7 shell-script parallelism
在 bash 脚本中,我有一个这样的程序
for i in {1..1000}
do
foo i
done
Run Code Online (Sandbox Code Playgroud)
我用参数调用函数foo
1000 次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
,ksh93
或bash
):
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
。
如果您可以分组运行,请嵌套一个循环:
#! /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)
#!/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)