【问题标题】:bash: limiting subshells in a for loop with file listbash:使用文件列表限制for循环中的子shell
【发布时间】:2015-02-18 08:07:11
【问题描述】:

我一直试图让一个 for 循环同时运行一堆命令,并试图通过子 shell 来实现。我设法将下面的脚本拼凑在一起进行测试,它似乎工作正常。

#!/bin/bash
for i in {1..255}; do
  (
    #commands
  )&

done
wait

唯一的问题是我的实际循环将是 for i in files* 然后它就崩溃了,我认为是因为它启动了太多子shell 来处理。所以我加了

#!/bin/bash
for i in files*; do
  (
    #commands
  )&
if (( $i % 10 == 0 )); then wait; fi
done
wait

现在失败了。有谁知道解决这个问题的方法?是使用不同的命令来限制子shell 的数量还是为$i 提供一个数字?

干杯

【问题讨论】:

  • 你对文件做了什么?
  • 这 - “然后它就崩溃了” - 很难相信。我很确定它会产生一些错误消息。

标签: bash for-loop subshell


【解决方案1】:

xargs/并行

另一种解决方案是使用为并发设计的工具:

printf '%s\0' files* | xargs -0 -P6 -n1 yourScript

-P6xargs 将启动的最大并发进程数。如果你愿意,可以设置为 10。

我建议xargs,因为它可能已经在您的系统上。如果您想要一个真正强大的解决方案,请查看GNU Parallel

数组中的文件名

对于您的问题的另一个明确答案:获取计数器作为数组索引?

files=( files* )
for i in "${!files[@]}"; do
    commands "${files[i]}" &
    (( i % 10 )) || wait
done

(复合命令周围的括号并不重要,因为后台作业与使用子shell具有相同的效果。)

功能

只是语义不同:

simultaneous() {
    while [[ $1 ]]; do
        for i in {1..11}; do
            [[ ${@:i:1} ]] || break
            commands "${@:i:1}" &
        done
        shift 10 || shift "$#"
        wait
    done
}
simultaneous files*

【讨论】:

  • wait -n 的另一个插件可以更快地开始新工作。
【解决方案2】:

您会发现使用jobs 来计算作业数量很有用。例如:

wc -w <<<$(jobs -p)

因此,您的代码将如下所示:

#!/bin/bash
for i in files*; do
  (
    #commands
  )&
  if (( $(wc -w <<<$(jobs -p)) % 10 == 0 )); then wait; fi
done
wait

正如@chepner 建议的那样:

在 bash 4.3 中,您可以在 任何 作业完成后立即使用 wait -n 继续,而不是等待所有作业完成

【讨论】:

    【解决方案3】:

    明确定义计数器

    #!/bin/bash
    for f in files*; do
      (
        #commands
      )&
      (( i++ % 10 == 0 )) && wait
    done
    wait
    

    不需要初始化i,因为第一次使用它会默认为0。也无需重新设置值,因为i %10 将在 i=10、20、30 等时为 0。

    【讨论】:

    • 我喜欢这个。更便宜+1
    • 再近看,我认为这还不够方便,因为您可以拥有i == 10,尽管后台作业的数量可能少于 10(它们可能会完成)。
    • 如果目标是让内核尽可能忙碌,我会使用像parallel 这样的作业调度程序,而不是在bash 中从头开始编写一个。这只是防止太多作业同时启动的一种方法,而不是让尽可能多的作业保持运行。
    • bash 4.3 中,您可以使用wait -n 等待任何单个作业完成,然后再开始下一个作业。但是,这会受到竞争条件的影响(我认为这是不可避免的),其中wait -n 在作业完成后被调用,这可能会导致一段时间内可以添加新作业,但是我们正在等待另一项工作完成。
    • 如果你真的想让所有的核心都忙,启动更多的进程,而不是一次运行,让操作系统来做调度。这对于 I/O 绑定的作业尤其理想,因为这些作业可能处于空闲状态,而其他进程可以运行。
    【解决方案4】:

    如果你的Bash≥4.3,可以使用wait -n

    #!/bin/bash
    
    max_nb_jobs=10
    
    for i in file*; do
        # Wait until there are less than max_nb_jobs jobs running
        while mapfile -t < <(jobs -pr) && ((${#MAPFILE[@]}>=max_nb_jobs)); do
            wait -n
        done
        {
            # Your commands here: no useless subshells! use grouping instead
        } &
    done
    wait
    

    如果您没有可用的wait -n,您可以使用以下内容:

    #!/bin/bash
    
    set -m
    
    max_nb_jobs=10
    
    sleep_jobs() {
       # This function sleeps until there are less than $1 jobs running
       local n=$1
       while mapfile -t < <(jobs -pr) && ((${#MAPFILE[@]}>=n)); do
          coproc read
          trap "echo >&${COPROC[1]}; trap '' SIGCHLD" SIGCHLD
          [[ $COPROC_PID ]] && wait $COPROC_PID
       done
    }
    
    for i in files*; do
        # Wait until there are less than 10 jobs running
        sleep_jobs "$max_nb_jobs"
        {
            # Your commands here: no useless subshells! use grouping instead
        } &
    done
    wait
    

    这样进行的好处是,我们不对完成工作所用的时间做任何假设。一旦有空间,新工作就会启动。此外,它都是纯 Bash,因此不依赖外部工具,并且(可能更重要的是),您可以使用您的 Bash 环境(变量、函数等)而不导出它们(数组不能轻易导出,因此可以成为一个伟大的专业人士)。

    【讨论】:

      猜你喜欢
      • 2012-02-26
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多