std::async 块甚至带有 std::launch::async 标志取决于是使用还是忽略返回的 future

std::async blocks even with std::launch::async flag depending on whether the returned future is used or ignored

问题描述

std::async 似乎即使使用 std::launch::async 标志也会阻塞:

#include <iostream>
#include <future>
#include <chrono>

int main(void)
{
    using namespace std::chrono_literals;

    auto f = [](const char* s)
    {
        std::cout << s;
        std::this_thread::sleep_for(2s);
        std::cout << s;
    };

    std::cout << "start\n";
    (void)std::async(std::launch::async, f, "1\n");
    std::cout << "in between\n";
    (void)std::async(std::launch::async, f, "2\n");
    std::cout << "end\n";

    return 0;
}

输出显示执行已序列化。即使有 std::launch::async 标志。

start
1
1
in between
2
2
end

但是如果我使用返回的std::future,它突然开始不阻塞了!

我所做的唯一更改是删除 (void) 并改为添加 auto r1 =

#include <iostream>
#include <future>
#include <chrono>

int main(void)
{
    using namespace std::chrono_literals;

    auto f = [](const char* s)
    {
        std::cout << s;
        std::this_thread::sleep_for(2s);
        std::cout << s;
    };

    std::cout << "start\n";
    auto r1 = std::async(std::launch::async, f, "1\n");
    std::cout << "in between\n";
    auto r2 = std::async(std::launch::async, f, "2\n");
    std::cout << "end\n";

    return 0;
}

而且,结果大不相同。它肯定表明执行是并行的。

start
in between
1
end
2
1
2

我为 CentOS devtoolset-7 使用了 gcc。

gcc (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

我的Makefile是:

.PHONY: all clean

all: foo

SRCS := $(shell find . -name '*.cpp')
OBJS := $(SRCS:.cpp=.o)

foo: $(OBJS)
        gcc -o $@ $^ -lstdc++ -pthread

%.o: %.cpp
        gcc -std=c++17 -c -g -Wall -O0 -pthread -o $@ $<

clean:
        rm -rf foo *.o

问题

规范中是否有此行为?

还是gcc实现的bug?

为什么会这样?

有人可以给我解释一下吗?

std::future 析构函数将 block if it’s a future from std::async and is the last reference to the shared state。我相信你在这里看到的是

  1. async return 的调用是 future,但是
  2. future 没有被捕获,所以
  3. future 的析构函数触发,
  4. 块,导致任务以串行方式完成。

显式捕获 return 值会导致两个析构函数仅在函数结束时触发,这会使两个任务 运行 直到完成。