在并行运行一定数量的命令方面的对比:对比 xargs -P、GNU parallel 和 "moreutils" parallel。

8
我正在尝试在bash脚本中运行26台服务器上的多个mongodump。
我可以同时运行3个命令,例如:
mongodump -h staging .... & mongodump -h production .... & mongodump -h web ... &
当一个mongodump完成后,我想启动另一个mongodump。
由于服务器将耗尽CPU,因此无法同时运行所有26个mongodumps命令。最多同时运行3个mongodumps。

你尝试过将每三个 & 替换为 ; 吗? - Jack
2个回答

13
您可以使用xarg-P选项并行运行指定数量的调用请注意,-P选项不是POSIX强制要求的, 但GNU的xargs和BSD/macOS的xargs都支持它。
xargs -P 3 -n 1 mongodump -h <<<'staging production web more stuff and so on'

这段代码会并行地运行 mongodump -h stagingmongodump -h productionmongodump -h web,等待所有三个调用完成后,继续执行 mongodump -h moremongodump -h stuffmongodump -h and 等等。 -n 1 从输入流中获取一个参数,并调用 mongodump;如有需要,可以单引号或双引号括起输入中的参数。
注意:只有 GNU 的 xargs 支持 -P 0,其中 0 表示“尽可能同时运行多个进程”。
默认情况下,通过 stdin 提供的参数会添加到指定命令的末尾。如果需要控制各自参数在生成的命令中的位置,
提供逐行的参数,使用 -I {} 来指示,并定义 {} 作为每个输入行的占位符。
xargs -P 3 -I {} mongodump -h {} after <<<$'staging\nproduction\nweb\nmore\nstuff'

现在,每个输入参数都会被替换为{},允许参数after在其后出现。

但是,请注意,每个输入行都不可避免地作为一个单一的参数传递。

BSD/macOS的xargs允许您将-n-J {}结合使用,而无需提供基于行的输入,但GNU的xargs不支持-J
简而言之:只有BSD/macOS允许您将输入参数的放置与一次读取多个参数相结合。

请注意,xargs不会并行序列化命令的标准输出,因此并行进程的输出可以交错到达
使用GNUparallel可以避免这个问题 - 请参见下文。


替代方案:parallel xargs的优点在于它是一个标准实用程序,因此在支持-P的平台上,没有任何先决条件。
在Linux世界中(尽管也可以通过Homebrew在macOS上使用),有两个专门用于并行运行命令的实用程序,不幸的是,它们共享相同的名称;通常,您必须按需安装它们:
  • parallel(一个二进制文件)来自moreutils软件包-请参见其主页

  • GNU parallel(一个Perl脚本)-更加强大-来自parallel软件包感谢,twalberg. -请参见其主页

如果您已经安装了一个 parallel 工具,那么使用 parallel --version 命令可以告诉您它是哪一个(GNU parallel 会显示版本号和版权信息,而 "moreutils" 的 parallel 则会提示无效选项并显示语法摘要)。

使用 "moreutils" 的 parallel 工具:

parallel -j 3 -n 1 mongodump -h -- staging production web more stuff and so on

# Using -i to control placement of the argument, via {}
# Only *1* argument at at time supported in that case.
parallel -j 3 -i mongodump -h {} after -- staging production web more stuff and so on

xargs不同,此parallel实现不会从stdin获取要传递的参数;所有要传递的参数都必须在命令行中通过--后面传递。
据我所知,这个parallel实现提供的唯一功能超出了xargs可以做到的是:
  • -l选项允许推迟进一步的调用,直到系统负载超过指定阈值以下。
  • 可能是这个(来自man页):"stdout和stderr通过相应的内部管道串行化,以防止烦人的并发输出行为。",但我发现在其man页日期为2009-07-2的版本中并非如此 - 见最后一节。
使用GNU parallel

Ole Tange致敬。

parallel -P 3 -n 1 mongodump -h <<<$'staging\nproduction\nweb\nmore\nstuff\nand\nso\non'

# Alternative, using ::: followed by the target-command arguments.
parallel -P 3 -n 1 mongodump -h ::: staging production web more stuff and so on 

# Using -n 1 and {} to control placement of the argument.
# Note that using -N rather than -n would allow per-argument placement control
# with {1}, {2}, ...
parallel -P 3 -n 1 mongodump -h {} after <<<$'staging\nproduction\nweb\nmore\nstuff\nand'

与xargs一样,传递参数是通过stdin提供的,但GNU parallel还支持将它们放在命令行上,在可配置的分隔符(默认为:::)之后。与xargs不同,每个输入行被视为单个参数。注意:如果您的命令涉及带引号的字符串,则必须使用-q将它们作为不同的参数传递;例如,parallel -q sh -c 'echo hi, $0' ::: there只能使用-q。与GNU xargs一样,您可以使用-P 0同时运行尽可能多的调用,充分利用机器的性能,这意味着,根据Ole的说法,“直到GNU Parallel达到限制(文件句柄和进程)”。方便的是,省略-P不仅会一次运行一个进程,而是会在每个CPU核心上运行一个进程。默认情况下,并行执行的命令的输出会自动按每个进程进行序列化(分组),以避免交织的输出。这通常是可取的,但请注意,这意味着只有在创建输出的第一个命令终止后,您才会开始看到其他命令的输出。使用--line-buffer选项(在更高版本中为--lb)退出此行为或-u(--ungroup)以允许单个输出行混合来自不同进程的输出;有关详细信息,请参阅手册。
GNU parallel旨在成为xargs的更好的继承者,提供了许多更多的功能:一个值得注意的例子是能够 对传递的参数执行复杂的转换,可选地基于Perl的正则表达式;另请参见:man parallelman parallel_tutorial

可选阅读:测试输出序列化行为

以下命令测试了xargs和两个parallel实现如何处理并行运行的命令交错输出的方式——它们是按照到达顺序显示输出,还是尝试对其进行序列化:

2个级别的序列化,都会引入开销:

  • 行级序列化:防止来自不同进程的部分行混合在单个输出行中。

  • 进程级序列化:确保给定进程的所有输出行被分组在一起。
    这是最用户友好的方法,但请注意,这意味着只有第一个创建输出的命令终止后,您才会开始以序列方式看到其他命令的输出。

据我所知,只有GNU的parallel提供了任何序列化(尽管“moreutils” parallel手册日期为2009年7月2日的页面[1]上说过),而且它支持两种方法。

以下命令假定存在可执行脚本 ./tst,其内容如下:
#!/usr/bin/env bash

printf "$$: [1/2] entering with arg(s): $*"
sleep $(( $RANDOM / 16384 ))
printf " $$: [2/2] finished entering\n"
echo "  $$: stderr line" >&2
echo "$$: stdout line"
sleep $(( $RANDOM / 8192 ))
echo "    $$: exiting"

(包括Ubuntu 16.04和macOS 10.12上发现的GNU和BSD/macOS实现):

不会进行串行化:单个输出行可以包含来自多个进程的输出。

$ xargs -P 3 -n 1 ./tst <<<'one two three'
2593: [1/2] entering with arg(s): one2594: [1/2] entering with arg(s): two 2593: [2/2] finished entering
  2593: stderr line
2593: stdout line
2596: [1/2] entering with arg(s): three   2593: exiting
 2594: [2/2] finished entering
  2594: stderr line
2594: stdout line
 2596: [2/2] finished entering
  2596: stderr line
2596: stdout line
   2594: exiting
   2596: exiting

"moreutils"中的"parallel" (版本的man页面日期为2009-07-02):
不会进行串行化:单个输出行可以包含来自多个进程的输出。
$ parallel -j 3 ./tst -- one two three
3940: [1/2] entering with arg(s): one3941: [1/2] entering with arg(s): two3942: [1/2] entering with arg(s): three 3941: [2/2] finished entering
  3941: stderr line
3941: stdout line
 3942: [2/2] finished entering
  3942: stderr line
3942: stdout line
 3940: [2/2] finished entering
  3940: stderr line
3940: stdout line
   3941: exiting
   3942: exiting

GNU parallel(版本20170122)

默认情况下,进程级别的串行化(分组)会发生。 使用--line-buffer(在更新版本中为--lb)选择行级别的串行化,或使用-u--ungroup)退出任何类型的串行化。

请注意,在每个组中,标准错误输出出现在标准输出之后(而版本20170122附带的手册声称标准错误输出首先出现)。

$ parallel -P 3 ./tst ::: one two three
2544: [1/2] entering with arg(s): one 2544: [2/2] finished entering
2544: stdout line
   2544: exiting
  2544: stderr line
2549: [1/2] entering with arg(s): three 2549: [2/2] finished entering
2549: stdout line
   2549: exiting
  2549: stderr line
2546: [1/2] entering with arg(s): two 2546: [2/2] finished entering
2546: stdout line
   2546: exiting
  2546: stderr line

“stdout和stderr通过相应的内部管道进行序列化,以防止烦人的并发输出行为。” 如果我漏掉了什么,请告诉我。

1
如果你仅排除每三个&(或者如果所有内容在一行上,则使用;),那么它将不会并行执行整个过程。
例如:
echo "Hello" & sleep 1 ;
echo "Hello Again" & sleep 1 ;
echo "Once More" & sleep 1 ;

针对长时间运行的后台进程,您需要使用wait命令来确保它们已经完成。 - mklement0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接