15分钟入门parallel
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