并行化 Bash FOR 循环

Rav*_*ill 180 shell-script gnu-parallel

我一直在尝试使用 GNU Parallel 并行化以下脚本,特别是三个 FOR 循环实例中的每一个,但未能实现。FOR 循环中包含的 4 个命令串联运行,每个循环大约需要 10 分钟。

#!/bin/bash

kar='KAR5'
runList='run2 run3 run4'
mkdir normFunc
for run in $runList
do 
  fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear

  rm -f *.mat
done
Run Code Online (Sandbox Code Playgroud)

PSk*_*cik 270

示例任务

task(){
   sleep 0.5; echo "$1";
}
Run Code Online (Sandbox Code Playgroud)

连续运行

for thing in a b c d e f g; do 
   task "$thing"
done
Run Code Online (Sandbox Code Playgroud)

并行运行

for thing in a b c d e f g; do 
  task "$thing" &
done
Run Code Online (Sandbox Code Playgroud)

在 N 进程批次中并行运行

N=4
(
for thing in a b c d e f g; do 
   ((i=i%N)); ((i++==0)) && wait
   task "$thing" & 
done
)
Run Code Online (Sandbox Code Playgroud)

也可以使用 FIFO 作为信号量,并使用它们来确保尽快产生新进程并且同时运行不超过 N 个进程。但它需要更多的代码。

具有基于 FIFO 的信号量的 N 个进程:

# initialize a semaphore with a given number of tokens
open_sem(){
    mkfifo pipe-$$
    exec 3<>pipe-$$
    rm pipe-$$
    local i=$1
    for((;i>0;i--)); do
        printf %s 000 >&3
    done
}

# run the given command asynchronously and pop/push tokens
run_with_lock(){
    local x
    # this read waits until there is something to read
    read -u 3 -n 3 x && ((0==x)) || exit $x
    (
     ( "$@"; )
    # push the return code of the command to the semaphore
    printf '%.3d' $? >&3
    )&
}

N=4
open_sem $N
for thing in {a..g}; do
    run_with_lock task $thing
done 
Run Code Online (Sandbox Code Playgroud)

解释:

我们通过推送 (= printf) 和弹出 (= read) 标记 ( '000') 来使用文件描述符 3 作为信号量。通过推送已执行任务的返回码,我们可以在出现问题时中止。

  • 里面有 `wait` 的那行基本上让所有进程运行,直到它遇到 `nth` 进程,然后等待所有其他进程完成运行,对吗? (7认同)
  • https://unix.stackexchange.com/a/436713/192211 建议使用更优雅的解决方案,即使用“wait -n”来摆脱纯 bash 而无需批处理。 (5认同)
  • @naught101 是的。`wait` 没有 arg 等待所有孩子。这样就有点浪费了。基于管道的信号量方法为您提供了更流畅的并发性(我一直在基于自定义 shell 的构建系统中使用它,并且现在成功检查了`-nt`/`-ot` 一段时间) (3认同)
  • 请注意,如果您设置了“set -e”,则第四个解决方案对您不起作用,您需要将其更改为“((++i==1))” (3认同)
  • 对于“具有基于 FIFO 的信号量的 N 个进程”,请记住在 for/done 循环后添加一个“等待”,以防止在执行最后一个任务时脚本进一步执行 (2认同)

gol*_*cks 149

你为什么不分叉(又名背景)它们?

foo () {
    local run=$1
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

for run in $runList; do foo "$run" & done
Run Code Online (Sandbox Code Playgroud)

如果不清楚,重要的部分在这里:

for run in $runList; do foo "$run" & done
                                   ^
Run Code Online (Sandbox Code Playgroud)

导致函数在后台分叉的 shell 中执行。那是平行的。

  • 您可能还想在最后添加一个 `wait` 命令,这样主脚本在所有后台作业都完成之前不会退出。 (38认同)
  • 如果我有 8 个文件要并行运行但只有 4 个内核,是否可以将其集成到这样的设置中,或者是否需要 Job Scheduler? (10认同)
  • 这就像一个魅力。谢谢你。这么简单的实现(现在让我觉得很愚蠢!)。 (8认同)
  • 在这种情况下,这并不重要。系统拥有比核心更多的活动进程是正常的。如果您有*许多短任务*,理想情况下您将提供一个由数量或工作线程服务的队列&lt;内核数量。我不知道用 shell 脚本真正完成的频率有多高(在这种情况下,它们不会是线程,它们会是独立的进程)但是对于 * 相对较少的长任务 * 这将是毫无意义的。操作系统调度程序会处理它们。 (6认同)
  • 我还认为限制并发进程的数量很有用:每个进程使用 100% 的核心时间大约 25 分钟。这是在一个具有 16 个核心的共享服务器上,很多人都在其中运行作业。我需要运行该脚本的 23 个副本。如果我同时运行它们,那么我就会淹没服务器,并使其在一两个小时内对其他人毫无用处(负载上升到 30,其他一切都会减慢)。我想可以用“nice”来完成,但我不知道它是否会完成。 (2认同)
  • 啊,PSkocik 下面的答案有一个非常简单的解决方案。 (2认同)

fro*_*utz 86

for stuff in things
do
( something
  with
  stuff ) &
done
wait # for all the something with stuff
Run Code Online (Sandbox Code Playgroud)

它是否真的有效取决于你的命令;我对他们不熟悉。的rm *.mat,如果它在并行运行看起来有点容易产生矛盾?

  • +1 为`wait`,我忘记了。 (14认同)
  • 如果有成吨的“东西”,这会不会启动成吨的流程?最好同时启动一定数量的进程,对吗? (9认同)
  • 这也完美运行。您说得对,我必须将 `rm *.mat` 更改为类似 `rm $run".mat"` 的内容,才能使其在一个进程不干扰另一个进程的情况下正常工作。**谢谢**。 (2认同)
  • 很有帮助的提示!这种情况下如何设置线程数? (2认同)

lev*_*lev 37

for stuff in things
do
sem -j+0 "something; \
  with; \
  stuff"
done
sem --wait
Run Code Online (Sandbox Code Playgroud)

这将使用信号量,并行化与可用内核数一样多的迭代(-j +0 表示您将并行化N+0 个作业,其中N 是可用内核数)。

sem --wait告诉在执行连续的代码行之前等待 for 循环中的所有迭代都终止执行。

注意:您将需要来自GNU 并行项目的“并行” (sudo apt-get install parallel)。


Tom*_*zka 26

最大 N 进程并发中的并行执行

只是一个香草bash脚本-无需外部库/应用需要

#!/bin/bash

N=4

for i in {a..z}; do
    (
        # .. do your stuff here
        echo "starting task $i.."
        sleep $(( (RANDOM % 3) + 1))
    ) &

    # allow to execute up to $N jobs in parallel
    if [[ $(jobs -r -p | wc -l) -ge $N ]]; then
        # now there are $N jobs already running, so wait here for any job
        # to be finished so there is a place to start next one.
        wait -n
    fi

done

# no more jobs to be started but wait for pending jobs
# (all need to be finished)
wait

echo "all done"
Run Code Online (Sandbox Code Playgroud)

并行处理文件列表的另一个示例:

#!/bin/bash

N=4

find ./my_pictures/ -name "*.jpg" | (
    while read filepath; do
        jpegoptim "${filepath}" &
        if [[ $(jobs -r -p | wc -l) -ge $N ]]; then wait -n; fi
    done;
    wait
)
Run Code Online (Sandbox Code Playgroud)

  • 对于没有外部库的每个人来说,这是一个可以理解和简单的方法。谢谢 (2认同)

小智 23

我经常使用的一种非常简单的方法:

cat "args" | xargs -P $NUM_PARALLEL command
Run Code Online (Sandbox Code Playgroud)

这将运行命令,同时传入“args”文件的每一行,最多同时运行 $NUM_PARALLEL。

如果您需要替换不同位置的输入参数,您还可以查看 xargs 的 -I 选项。

  • 如果您有例如要处理的文件名列表,这可以很好地工作。但如果你是迂腐的,它真的不是一个 for 循环。尽管如此,我发现解决方案很优雅。 (2认同)

Ole*_*nge 10

似乎 fsl 作业相互依赖,因此 4 个作业不能并行运行。但是,这些运行可以并行运行。

使 bash 函数运行一次并并行运行该函数:

#!/bin/bash

myfunc() {
    run=$1
    kar='KAR5'
    mkdir normFunc
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

export -f myfunc
parallel myfunc ::: run2 run3 run4
Run Code Online (Sandbox Code Playgroud)

要了解更多信息,请观看介绍视频:https : //www.youtube.com/playlist? list =PL284C9FF2488BC6D1并花一个小时浏览教程http://www.gnu.org/software/parallel/parallel_tutorial.html您的命令line 会喜欢你的。


小智 6

我真的很喜欢@lev 的答案,因为它以非常简单的方式提供了对最大进程数的控制。但是,如手册中所述, sem 不适用于括号。

for stuff in things
do
sem -j +0 "something; \
  with; \
  stuff"
done
sem --wait
Run Code Online (Sandbox Code Playgroud)

做这份工作。

-j +N 将 N 添加到 CPU 内核数。并行运行这么多作业。对于计算密集型作业 -j +0 很有用,因为它会同时运行 CPU 核心数的作业。

-j -N 从 CPU 内核数中减去 N。并行运行这么多作业。如果评估的数字小于 1,则将使用 1。另请参阅--use-cpus-instead-of-cores。