100 Advanced The Hundred
The async That Runs in Series
fire and forget, minus the forget
using namespace std::chrono;
void upload() { std::this_thread::sleep_for(milliseconds(200)); }
long elapsed_ms(steady_clock::time_point t0) {
return duration_cast<milliseconds>(steady_clock::now() - t0).count();
}
int main() {
auto t0 = steady_clock::now();
(void)std::async(std::launch::async, upload); // fire...
(void)std::async(std::launch::async, upload); // ...and forget
std::cout << "fire-and-forget: " << elapsed_ms(t0) << " ms\n";
t0 = steady_clock::now();
auto a = std::async(std::launch::async, upload);
auto b = std::async(std::launch::async, upload);
a.wait();
b.wait();
std::cout << "futures kept: " << elapsed_ms(t0) << " ms\n";
}
Run it. What do the two timings say?
Answer
Roughly fire-and-forget: 400 ms — the "forgotten" launches ran one after the
other, and main waited at each semicolon. The kept futures overlap: roughly 200 ms.
Why¶
The future returned by std::async is special: as the last handle to the task's shared
state, its destructor blocks until the task finishes. Discard the return value and
that future is a temporary, dead at the semicolon — each "background" launch waits right
there, mid-statement, and the two uploads run in series: 200 + 200 ≈ 400 ms. The design
is deliberate (a detached task could outlive locals it references), but it turns a bare
std::async(...) into a slow synchronous call. libstdc++ marks std::async
[[nodiscard]], so plain discarding earns "ignoring return value ... declared with
attribute 'nodiscard'" (-Wunused-result, on even without -Wall) — the (void) cast
above is the customary silencer, wait included. Only std::async futures do this; one
from a std::promise or std::packaged_task destroys without waiting.
The fix¶
Name the futures — the join moves to end of scope, and the tasks truly overlap:
auto f1 = std::async(std::launch::async, upload); // named → runs in parallel
auto f2 = std::async(std::launch::async, upload); // dtors join at scope exit
std::thread(upload).detach(); // no result wanted? skip async (or C++20 std::jthread)
Takeaway: std::async's future is an RAII join — drop it on the floor and you wait
on the spot.
Try it: g++ -std=c++17 -pthread main.cpp -o demo && ./demo