Search code examples
c++11stdasync

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


Description of the problem

std::async seems to block even with std::launch::async flag:

#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;
}

output shows that the execution is serialized. Even with std::launch::async flag.

start
1
1
in between
2
2
end

But if I use returned std::future, it suddenly starts to not block!

The only change I made is removing (void) and adding auto r1 = instead:

#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;
}

And, the result is quite different. It definitely shows that the execution is in parallel.

start
in between
1
end
2
1
2

I used gcc for CentOS devtoolset-7.

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.

My Makefile is:

.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

Question

Is this behaviour in the specification?

Or is it a gcc implementation bug?

Why does this happen?

Can someone explain this to me, please?


Solution

  • The std::future destructor will block if it’s a future from std::async and is the last reference to the shared state. I believe what you’re seeing here is

    1. the call to async returns a future, but
    2. that future is not being captured, so
    3. the destructor for that future fires, which
    4. blocks, causing the tasks to be done in serial.

    Explicitly capturing the return value causes the two destructors to fire only at the end of the function, which leaves both tasks running until they’re done.