Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a safe way to call wait() on std::future?

Tags:

c++

c++11

The C++11 standard says:

30.6.6 Class template future

(3) "The effect of calling any member function other than the destructor, the move-assignment operator, or valid on a future object for which valid() == false is undefined."

So, does it mean that the following code might encounter undefined behaviour?

void wait_for_future(std::future<void> & f)
{
    if (f.valid()) {
        // what if another thread meanwhile calls get() on f (which invalidates f)?
        f.wait();
    }
    else {
        return;
    }
}

Q1: Is this really a possible undefined behaviour?

Q2: Is there any standard compliant way to avoid the possible undefined behaviour?

Note that the standard has an interesting note [also in 30.6.6 (3)]:

"[Note: Implementations are encouraged to detect this case and throw an object of type future_error with an error condition of future_errc::no_state. —endnote]"

Q3: Is it ok if I just rely on the standard's note and just use f.wait() without checking f's validity?

void wait_for_future(std::future<void> & f)
{
    try {
        f.wait();
    }
    catch (std::future_error const & err) {
        return;
    }
}

EDIT: Summary after receiving the answers and further research on the topic

As it turned out, the real problem with my example was not directly due to parallel modifications (a single modifying get was called from a single thread, the other thread called valid and wait which shall be safe).

The real problem was that the std::future object's get function was accessed from a different thread, which is not the intended use case! The std::future object shall only be used from a single thread!

The only other thread that is involved is the thread that sets the shared state: via return from the function passed to std::async or calling set_value on the related std::promise object, etc.

More: even waiting on an std::future object from another thread is not intended behaviour (due to the very same UB as in my example#1). We shall use std::shared_future for this use case, having each thread its own copy of an std::shared_future object. Note that all these are not through the same shared std::future object, but through separate (related) objects!

Bottom line: These objects shall not be shared between threads. Use a separate (related) object in each thread.

like image 565
Norbert Bérci Avatar asked Dec 11 '14 12:12

Norbert Bérci


Video Answer


2 Answers

A normal std::future is not threadsafe by itself. So yes it is UB, if you call modifying functions from multiple threads on a single std::future as you have a potential race condition. Though, calling wait from multiple threads is ok as it's const/non-modifying.

However, if you really need to access the return value of a std::future from multiple threads you can first call std::future::share on the future to get a std::shared_future which you can copy to each thread and then each thread can call get. Note that it's important that each thread has its own std::shared_future object.

You only need to check valid if it is somehow possible that your future might be invalid which is not the case for the normal usecases(std::async etc.) and proper usage(e.g.: not callig get twice).

like image 186
Stephan Dollberg Avatar answered Oct 16 '22 15:10

Stephan Dollberg


Futures allow you to store the state from one thread and retrieve it from another. They don't provide any further thread safety.

Is this really a possible undefined behaviour?

If you have two threads trying to get the future's state without synchronisation, yes. I've no idea why you might do that though.

Is there any standard compliant way to avoid the possible undefined behaviour?

Only try to get the state from one thread; or, if you genuinely need to share it between threads, use a mutex or other synchronisation.

Is it ok if I just rely on the standard's note

If you known that the only implementations you need to support follow that recommendation, yes. But there should be no need.

and just use f.wait() without checking f's validity?

If you're not doing any weird shenanigans with multiple threads accessing the future, then you can just assume that it's valid until you've retrieved the state (or moved it to another future).

like image 7
Mike Seymour Avatar answered Oct 16 '22 16:10

Mike Seymour