并行运行可指定数量的命令 - 对比xargs -P,GNU parallel和"moreutils"parallel

bas*_*nte 4 parallel-processing bash xargs gnu-parallel

我试图在bash脚本中在26台服务器上运行多个mongodump.

我可以运行3个命令

mongodump -h staging .... & mongodump -h production .... & mongodump -h web ... &

同时,当一个人完成时,我想开始另一个mongodump.

我无法同时运行所有26个mongodumps命令,服务器将在CPU上运行.最多3个mongodumps同时.

mkl*_*nt0 6

您可以使用xarg-P选项,以运行并行调用的可指定号码:

请注意,该-P选项不是POSIX强制要求的,但GNU xargs和BSD/macOS都xargs支持它.

xargs -P 3 -n 1 mongodump -h <<<'staging production web more stuff and so on'
Run Code Online (Sandbox Code Playgroud)

这将运行mongodump -h staging, mongodump -h production以及 mongodump -h web并行,等待所有3个电话到结束,然后继续mongodump -h more, mongodump -h stuffmongodump -h and,等等.

-n 1抓起从输入流和呼叫参数mongodump; 如有必要,根据需要调整输入中的单引号或双引号.

注:GNU xargs -但不是BSD xargs-支持-P 0,这里0表示:"同时运行尽可能多的进程尽可能"

默认情况下,通过stdin提供的参数将附加到指定的命令.
如果需要控制生成的命令中放置相应参数的位置,

  • 逐行提供参数
  • 用于-I {}指示,并定义{}为每个输入行的占位符.
xargs -P 3 -I {} mongodump -h {} after <<<$'staging\nproduction\nweb\nmore\nstuff'
Run Code Online (Sandbox Code Playgroud)

现在每个输入参数都被替换{},允许参数after后来.

但请注意,每个输入行总是作为单个参数传递.

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

请注意,xargs没有序列从并行命令标准输出的输出,使从并行处理输出可以到达交错.
使用GNU parallel来避免这个问题 - 见下文.


替代方案: 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
Run Code Online (Sandbox Code Playgroud)

xargs此不同,此parallel实现不会将参数从stdin传递出去; 必须在命令行上传递所有传递参数,如下所示--.

据我所知,parallel除了xargs可以做的以外,这个实现提供的唯一功能是:

  • -l选项允许延迟进一步的调用,直到系统加载超额低于指定的阈值.
  • 可能是这个(来自man页面):"stdout和stderr是通过相应的内部管道序列化的,以防止令人讨厌的并发输出行为."虽然我发现在页面日期为2009- 的版本中并非如此man- 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'
Run Code Online (Sandbox Code Playgroud)
  • 与之一样xargs,传递参数是通过stdin提供的,但是GNU parallel还支持在可配置的分隔符(:::默认情况下)之后将它们放在命令行上.

  • 与之不同xargs,每个输入被视为单个参数.

  • 警告:如果你的命令涉及带引号的字符串,你必须使用-q它们作为不同的参数传递它们; 例如,parallel -q sh -c 'echo hi, $0' ::: there只适用于-q.

  • GNU xargs,你可以使用-P 0运行尽可能多的调用尽可能一次,取本机的功能和优势,这意味着,根据OLE"直到GNU并行击中限制(文件句柄和工艺)".

    • 便利,遗漏 -P不只是运行一个在一个时间过程中,由于其他实用程序做,但运行一个进程每个CPU核心.
  • 默认情况下,并行执行的命令输出会按进程自动序列化(分组),以避免交错输出.

    • 这通常是可取的,但请注意,这意味着只有在创建输出的第一个命令终止后,您才会开始看到其他命令的输出.
    • 使用选项--line-buffer(--lb在更新的版本中)选择退出此行为或
      -u(--ungroup)以允许甚至单个输出行混合来自不同进程的输出; 有关详细信息,请参阅手册

GNU parallel,旨在成为更好的继承者xargs,提供了更多的功能:一个值得注意的例子是能够对传递参数执行复杂的转换,可选择基于Perl 正则表达式 ; 另见:man parallelman parallel_tutorial.


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

以下命令测试xargs两个parallel实现如何处理并行运行的命令的交错输出 - 无论它们是在到达时显示输出还是尝试序列化它:

两个级别的序列化,这两个级别都引入了开销:

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

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

据我所知,只有GNU parallel提供任何序列化(尽管parallel2009-07-2 所述的"moreutils" 手册页[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"
Run Code Online (Sandbox Code Playgroud)

xargs (在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
Run Code Online (Sandbox Code Playgroud)

"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
Run Code Online (Sandbox Code Playgroud)

GNUparallel(版本20170122)

默认情况下会发生进程级序列化(分组).使用--line-buffer(--lb在较新的版本中)来选择行级序列化,或者选择退出任何类型的序列化-u
(--ungroup).

请注意,在每个组中,stderr输出是 stdout输出之后出现的(而版本20170122附带的手册页声称stderr输出首先出现).

$ 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
Run Code Online (Sandbox Code Playgroud)

[1]"stdout和stderr通过相应的内部管道序列化,以防止恼人的并发输出行为."
告诉我我是否遗漏了什么.


Jac*_*ack 1

如果您只是排除每第三个&(或者如果全部都在一行上则使用 a ;),那么它将不会并行执行整个过程。

例如:

echo "Hello" & sleep 1 ;
echo "Hello Again" & sleep 1 ;
echo "Once More" & sleep 1 ;
Run Code Online (Sandbox Code Playgroud)