Is standard guarantee safe usage of std::future after moving std::packaged_task?

Viewed 106

Let's say we have following code:

#include <iostream>
#include <future>

int main() {
    auto packagedTask = std::packaged_task<int()>([] {
        std::cout << "hello!\n";
        return 10;
        });

    auto packagedTaskFuture = packagedTask.get_future();
    auto packagedTaskPtr = std::make_shared<decltype(packagedTask)>(std::move(packagedTask));

    auto v1 = packagedTaskFuture.valid(); // is valid
    auto v2 = packagedTaskFuture.wait_for(std::chrono::seconds(0)); // timeout state
    (*packagedTaskPtr)(); // execute task
    auto v3 = packagedTaskFuture.wait_for(std::chrono::seconds(1)); // ready state
    auto v4 = packagedTaskFuture.get(); // 10

    return 0;
}

It works perfect on my Visual Studio environment, as you see I retrieve std::future right before I moving std::packaged_task to newly created std::shared_ptr. I've took a look into C++ standard about std::packaged_task, so §30.6.9.1 packaged_task(packaged_task&& rhs) noexcept p.6:

Effects: constructs a new packaged_task object and transfers ownership of rhs’s shared state to *this, leaving rhs with no shared state. Moves the stored task from rhs to *this.

and §30.6.9 p.2 says:

When the packaged_task object is invoked, its stored task is invoked and the result (whether normal or exceptional) stored in the shared state. Any futures that share the shared state will then be able to access the stored result.

Based on this info I have two questions:

1) Am I right saying that std::packaged_task would be linked to the same std::future after std::move?

2) Is it safe to use my std::shared_ptr in other thread and execute task there, checking std::future from current one?

1 Answers

So the standard talks about a (hidden) shared state. That shared state is moved on std::move-based copies.

The objects -- packaged task, future -- should be thought of as various kinds of smart pointers that have references to that shared state.

There is zero race conditions that can be caused by that shared state being interacted with in multiple threads. The only race conditions that can happen is if you are interacting with the same object from multiple threads, and in at least one thread you call a non-const method.

The "shared state" in the C++ concurrency primitives is conceptual, and not guaranteed to even be an object. It has no methods.

So, yes and yes.

The section in question is 33.6.5 Shared state[futures.state]. /9directly talks about synchronization (which is basically "no race conditions"):

Calls to functions that successfully set the stored result of a shared state synchronize with (6.8.2) calls to functions successfully detecting the ready state resulting from that setting. The storage of the result (whether normal or exceptional) into the shared state synchronizes with (6.8.2) the successful return from a call to awaiting function on the shared state.

The wording about move is spread over the various classes that own a shared state, plus the entire description of what a shared state is. That is a bit too much of the standard to quote.

Related