【问题标题】:make -j behaves differently with and without sub makesmake -j 有和没有 sub make 的行为不同
【发布时间】:2019-11-24 21:36:20
【问题描述】:

有人能解释一下为什么 make 和 sub-make 有不同的并行化行为吗?

下面有两个例子。一方面,所有目标都在一个 Makefile 中,如在第二个示例中,每个目标都有自己的文件夹/makefile,并且它们使用 $(MAKE) 相互调用。

使用-j 运行它们会产生截然不同的输出。在第一种情况下,对顶级依赖项(下面的a.out)的所有访问都是同步的。 Make 在运行任何依赖它的目标之前等待目标构建。

在第二种情况下,顶级目标(下面的a.out)受到并发访问。除非我们使用自己的同步技术来克服这个问题,否则这会给我们带来严重的问题。

示例 1:

jesaremi@u16-3:~/maketest$ cat Makefile
.ONESHELL:

all:  d.out b.out c.out a.out

a.out: CALLER ?= self
a.out:
        @
        echo entering $@ "(called by $(CALLER))"
        sleep 10
        echo exiting $@

b.out:  a.out
        @
        echo entering $@
        sleep 1
        echo exiting $@

c.out: a.out
        @
        echo entering $@
        sleep 1
        echo exiting $@

d.out: a.out
        @
        echo entering $@
        sleep 1
        echo exiting $@

jesaremi@u16-3:~/maketest$ make
entering a.out (called by self)
exiting a.out
entering d.out
exiting d.out
entering b.out
exiting b.out
entering c.out
exiting c.out



jesaremi@u16-3:~/maketest$ make -j
entering a.out (called by self)
exiting a.out
entering d.out
entering c.out
entering b.out
exiting c.out
exiting d.out
exiting b.out

示例 2(使用子制作):

jesaremi@js-u16-1:~/maketest$ cat */Makefile

---------- a/Makefile -------------
.ONESHELL:

a.out: CALLER ?= self
a.out:
        @
        echo entering $@ "(called by $(CALLER))"
        sleep 10
        echo exiting $@

---------- b/Makefile -------------
.ONESHELL:

export CALLER:=b

a.out:
        $(MAKE) -C ../a

b.out:  a.out
        @
        echo entering $@
        sleep 1
        echo exiting $@

---------- c/Makefile -------------
.ONESHELL:

export  CALLER:=c

a.out:
        $(MAKE) -C ../a

c.out: a.out
        @
        echo entering $@
        sleep 1
        echo exiting $@

---------- d/Makefile -------------
.ONESHELL:

export CALLER:=d

a.out:
        $(MAKE) -C ../a

d.out: a.out
        @
        echo entering $@
        sleep 1
        echo exiting $@


jesaremi@js-u16-1:~/maketest$ cat Makefile
all: d.out c.out b.out a.out

%.out:
        $(MAKE) -C $*

.PHONY:all



jesaremi@js-u16-1:~/maketest$ make
make -C d
make[1]: Entering directory '/home/jesaremi/maketest/d'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by d)
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/d'
make -C c
make[1]: Entering directory '/home/jesaremi/maketest/c'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by c)
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/c'
make -C b
make[1]: Entering directory '/home/jesaremi/maketest/b'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by b)
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/b'
make -C a
make[1]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by self)
exiting a.out
make[1]: Leaving directory '/home/jesaremi/maketest/a'





jesaremi@js-u16-1:~/maketest$ make -j
make -C d
make -C c
make -C b
make -C a
make[1]: Entering directory '/home/jesaremi/maketest/d'
make -C ../a
make[1]: Entering directory '/home/jesaremi/maketest/c'
make -C ../a
make[1]: Entering directory '/home/jesaremi/maketest/b'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
make[1]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by self)
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by c)
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by d)
entering a.out (called by b)
exiting a.out
make[1]: Leaving directory '/home/jesaremi/maketest/a'
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
exiting a.out
make[1]: Leaving directory '/home/jesaremi/maketest/c'
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/d'
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/b'

【问题讨论】:

  • 我们无能为力,因为您遗漏了最关键的信息:第二个示例中的根级 makefile 的内容。破译cat */Makefile 的输出也很困难,因为我们不知道一个makefile 在哪里结束,下一个从哪里开始。
  • 我认为这与递归使用 make 没有任何关系。发生这种情况是因为在第二种情况下,每个配方都会在输出中打印几行。
  • @MadScientist 我添加了缺失的信息
  • 第二个例子中的@HolyBlackCat 注意第一次执行(无-j)和第二次执行(有-j)的区别。您应该能够清楚地看到目标 a.out 的配方打印交织在一起,因为它同时调用了多次
  • 如果您想要多个 makefile 仅用于组织目的,请使用 include,并将它们命名为 something.make 而不是 Makefile

标签: makefile gnu-make


【解决方案1】:

也许您认为 make 的递归调用彼此通信 他们正在构建的目标并以某种方式互锁,因此没有两个子制造试图同时构建相同的目标.

这远非 make 所能做到的。 make 所能做的就是通知其他子制作有多少个目标他们正在构建,这样你就可以确保调用的目标总数不超过 N 个(对于-jN) .

如果您想确保两个不同的 makefile 不会尝试构建相同的目标,那么您可以通过组织 makefile 来完成; make 不能为你做。

在第二个示例中,您将所有先决条件列为一个目标:

all: d.out c.out b.out a.out

这个配方说,“在all 可以构建之前,目标d.outc.outb.outa.out 必须完成”。这没有说明先决条件(d.outc.outb.outa.out)本身之间的相对关系,因此当您使用 -j 运行时,子组件适用于所有人其中将同时调用。如果它们中的每一个都试图构建相同的共享目标,那么它们将相互干扰。

如果您想确保不会发生这种情况,您必须在您的 makefile 中声明先决条件关系,以便 make 知道它。例如,您可以这样做:

all: d.out c.out b.out a.out

d.out c.out b.out: a.out

%.out:
        $(MAKE) -C $*

.PHONY:all

现在,构建 d.outc.outb.out 的配方将在构建 a.out 的配方完成之前启动。

【讨论】:

  • 感谢详细解释
猜你喜欢
  • 1970-01-01
  • 2013-12-01
  • 2019-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 2011-01-17
相关资源
最近更新 更多