美文网首页
15分钟入门parallel

15分钟入门parallel

作者: 测试生财 | 来源:发表于2020-07-15 16:10 被阅读0次

    GNU Parallel是一个Linux下的工具,为了在一台或多台计算机上并行的执行计算任务,一个计算任务可以是一条shell命令或者一个以每一行做为输入的脚本程序。通常的输入是文件列表、主机列表、用户列表、URL列表或者表格列表;一个计算任务也可以是一个从管道读取的一条命令。GNU Parallel会把输入分块,然后通过管道并行的执行。

    如果你会使用xargs和tee命令,你会发现GNU Parallel非常易于使用,因为GNU Parallel具有与xargs一样的选项。GNU Parallel可以替代大部分的shell循环,并且用并行的方式更快的完成计算任务。

    GNU Parallel保证它的输出与顺序执行计算任务时是一样的,这样就可以方便的把GNU Parallel的输出做为其它程序的输入。

    对于每一行输入,GNU Parallel会把这一行做为参数来运行指定的命令。如果没有给出命令,那么这一行会被当做命令执行。多行输入会并行的运行。GNU Parallel经常被用于替代xargs或者cat | bash。

                                                                                          ---引用自某网络文章                      

    2.1 入门小例子

    下面是一个小例子,帮助你了解Parallel的威力:

    假设你已经知道seq命令的用途,在linux执行seq 5,会得到如下结果:

    root@i-s0tsk03r:~# seq 5

    1

    2

    3

    4

    5

    试下执行seq 5 | parallel seq {} '>' example.{},这条命令相当于:

    seq 1 > example.1

    seq 2 > example.2

    seq 3 > example.3

    seq 4 > example.4

    seq 5 > example.5

    seq 5生成了一个列表,包含“1,2,3,4,5”这五个元素,通过管道符"|"传递给Parrallel,Parrallel中的‘{}’类似于占位符,所以就变成了上述代码块中的五行代码,这里有个小技巧,使用--dry-run选项可以打印出来Parrallel实际执行的命令:seq 5 | parallel --dry-run seq {} '>' example.{}

    2.2 输入源

    GNU Parallel从输入源中读取数据,每个输入源都是一个命令行,输入源跟着‘:::’这个符号后面:

    parallel echo ::: 1 2 3 4 5

    命令结果:(顺序可能和实际有所不同)

    1

    2

    3

    4

    5

    如果程序的输入源是一些文件的时候,那么这个时候使用Parallel将非常便捷,例如:

    parallel wc ::: example.*

    根据上面入门小例子生成的五个文件(example.1,example.2,example.3,example.4,example.5),使用--dry-run选项,实际执行的应该是如下命令:

    wc example.1

    wc example.2

    wc example.3

    wc example.4

    wc example.5

          程序运行结果是

    1 1 2 example.1

    2 2 4 example.2

    3 3 6 example.3

    4 4 8 example.4

    5  5 10 example.5

          如果你使用了多个:::,GNU Parallel将生成所有输入源的组合

    parallel echo ::: S M L ::: Green Red

          同样的,我们使用--dry-run命令看看实际执行的是什么

    echo S Green

    echo S Red

    echo M Green

    echo M Red

    echo L Green

    echo L Red

          结果当然会输出六行,相当于输入源3*2=6

    Parallel也支持从标准输入读入,类似最开始seq的例子,下面是一个新的例子:

    find example.* | parallel echo File

    我们使用--dry-run命令看看实际执行的是什么

    echo File example.1

    echo File example.2

    echo File example.3

    echo File example.4

    echo File example.5

            这个命令其实相当于:

    parallel echo File ::: example.*

    2.3 构建命令行

            Shell命令是在:::之前的,我们可以为为命令行添加命令行选项:

    parallel wc -l :: example.*

    在这里我们为wc指定了选项-l,输出结果为(顺序可能和实际有所不同):

    1 example.1

    2 example.2

    3 example.3

    4 example.4

    5 example.5

    上述的命令里面可以包含多条Shell命令(或者多个程序),只要保证每条命令之间用";"分割开来即可(shell的语法,多个命令或者程序写在一行需要用“;”分割)

    执行:parallel --dry-run echo counting lines';' wc -l ::: example.*

    echo counting lines; wc -l example.1

    echo counting lines; wc -l example.2

    echo counting lines; wc -l example.3

    echo counting lines; wc -l example.4

    echo counting lines; wc -l example.5

    输出结果为(顺序可能和实际有所不同):

    counting lines

    1 example.1

    counting lines

    2 example.2

    counting lines

    3 example.3

    counting lines

    4 example.4

    counting lines

    5 example.5

    输入源的值通常附加到命令后面,通过使用{},我们可以在任意地方把输入源的值替换到{}:

    parallel  --dry-run echo counting {}';' wc -l {} ::: example.*

    实际的命令如下:

    echo counting example.1; wc -l example.1

    echo counting example.2; wc -l example.2

    echo counting example.3; wc -l example.3

    echo counting example.4; wc -l example.4

    echo counting example.5; wc -l example.5

          {}替换了example.*中的每个值,实际上{}只适用于只有一个:::的情况,如果有多个:::,我们可以分别使用{1},{2}...{n}来替代每个:::后的输入源,下面是一个例子:

    parallel --dry-run echo count {1} in {2}';' wc {1} {2} ::: -l -c ::: example.*

    #这个例子比较复杂,我们需要用dry-run看看实际到底执行了什么,实际执行的命令如下:

    echo count -l in example.1; wc -l example.1

    echo count -l in example.1; wc -l example.1

    echo count -l in example.2; wc -l example.2

    echo count -l in example.3; wc -l example.3

    echo count -l in example.4; wc -l example.4

    echo count -l in example.5; wc -l example.5

    echo count -c in example.1; wc -c example.1

    echo count -c in example.2; wc -c example.2

    echo count -c in example.3; wc -c example.3

    echo count -c in example.4; wc -c example.4

    echo count -c in example.5; wc -c example.5

    先看下输入源:

    第一个输入源:::对应的是-l -c

    第二个输入源:::对应的是example.*

          所以{1}代表的是{-l  -c}这个集合,{2}代表的是{example.1 example.2 example.3 example.4 example.5}这个集合,然后根据组合2*5=10,一共生成了如上的十个命令。

          看到这里,如果你考虑用parallel去改造你之前的shell中的循环,那应该是一个相当棒的主意!

    2.4 输出控制

          Parallel的输出会随着所有命令结束而被立即打印出来,这就意味着输出的顺序可能和输入的顺序不完全相同,例如:

    parallel sleep {}';' echo {} done ::: 5 4 3 2 1

    #这个命令一眼看上去结果应该是

    5 done

    4 done

    3 done

    2 done

    1 done

    #但是实际可能是

    1 done

    2 done

    3 done

    4 done

    5 done

    原因是什么呢,可能是因为sleep的时候,多进程切换的顺序是不固定的,如果我们想要强制有序输出,那么可以指定参数--keep-order/-k,这样得到的结果就会是有序的,例

    parallel -k sleep {}';' echo {} done ::: 5 4 3 2 1

    输出结果为(结果顺序一定是预期的):

    5 done

    4 done

    3 done

    2 done

    1 done

    2.5 执行控制

    如果你的任务是计算密集型的,Parallel将帮助你在每个CPU上运行一个任务,达到并行的效果。

    但是有时候你希望能够运行更多的任务,你可以通过-j/--jobs选项控制任务槽(执行任务的单元)。为parallel里面指定--jobs参数,这里我们可以指定parallel运行槽为2:

    parallel --jobs 2 sleep {}';' echo "jobs:" {%} 'echo' {} done ::: 5 4 3 1 2

    输出结果为(顺序可能和实际有所不同):

    jobs: 2 echo 4 done

    jobs: 1 echo 5 done

    jobs: 1 echo 1 done

    jobs: 2 echo 3 done

    jobs: 1 echo 2 done

    两个job的槽会花1~5分钟来完成这五个任务的处理。在这里,我们使用了{%}来打印job的id(类似于进程id)。可以看到五个输入被分为如下的序列:

    Job slot 1:5 1 2

    Job slot 2:4 3

    当然了,你可以通过指定'--job 5'让五个任务并行起来,这样所有的五个任务会同时启动,但是它们会在不同时刻结束。

    parallel --jobs 5 sleep {}';' echo "jobs:" {%} 'echo' {} done ::: 5 4 3 1 2

    输出

    jobs: 4 echo 1 done

    jobs: 5 echo 2 done

    jobs: 3 echo 3 done

    jobs: 2 echo 4 done

    jobs: 1 echo 5 done

    所有的任务都是并行运行的:

    Job slot 1:5

    Job slot 2:4

    Job slot 3:3

    Job slot 4:1

    Job slot 5:2

    你可以传递'--job 0'来尽量让任务跑满所有CPU,而不是手动指定job的number,这样效率会更高。

    2.6 管道模式

    Parallel可以通过标准输入传递数据块给命令行:

    seq 1000000 | parallel --pipe wc

    #这里比较疑惑地方是加--pipe和不加该选项有什么区别呢?

    #seq 1000000 | wc,相当于生成了一个数据块,通过标准输入给了wc,所以结果应该是

    1000000 1000000 6888896

    #wc命令会打印出行数,字符数,字节数,拿seq 11举例子,输出应该是

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    #那么,seq 11 | wc ,应该是11,11,24,第一个11代表有11行,第二个11代表有11个字符,第三个24相当于(1到9,10拆分成1和0,11拆分成1和1,然后一共附加11个换行符)

    #如果不加--pipe呢,seq 100000 | parallel  wc会报错,此时找不到wc 1到wc 100000这些命令

    seq 1000000 | parallel --pipe wc的输出(顺序可能和实际有所不同):

    165668  165668 1048571

    149796  149796 1048572

    149796  149796 1048572

    149796  149796 1048572

    149796  149796 1048572

      85352  85352  597465

    149796  149796 1048572

    这样相当于parallel内部对数据块进行了切开,然后并行多个wc去处理一小块数据,没有写代码就轻松实现了并行的大数据文件处理,是不是很神奇!

    Tips:GNU Parallel会对大数据块以'\n'进行拆分(即加一个换行符),拆分后的每个部分大小不会超过1MB,所以对于大数据块的程序处理机器有帮助。

    2.7 小结

    相信你已经掌握了GNU Parallel的基本使用方法,在大部分的场景下,可能已经够用了。

    剩下的部分会讨论更多Parallel的使用细节,覆盖更多的使用场景。

    个人声明:本文翻译源于GNU Parallel 2018官方文档,很多地方加入了自己的理解,由于翻译水平有限,可能存有勘误。如果有涉及到版权问题,请直接邮件与我联系:ccg_const2010@163.com

    相关文章

      网友评论

          本文标题:15分钟入门parallel

          本文链接:https://www.haomeiwen.com/subject/zkcjhktx.html