Sergey Kolesnik
Sergey Kolesnik

Reputation: 3650

non-blocking call of std::async: how is this version dangerous?

Some time ago I was looking for a way to invoke std::async without the need of storing std::future, thus not blocking the execution at the end of the scope. I found this answer which uses a captured std::shared_ptr for an std::future, therefore allowing to make a nonblocking call to std::async.

Another way of deferring a destructor invocation is to prevent it from to be called at all. This can be achieved with in-place construction with operator new.

Consider this version that uses a static thread local storage for an in-place constructed std::future<void>:

template <class F>
void call_async(F&& fun) {
    thread_local uint8_t buf[sizeof(std::future<void>)] = {0};
    auto fut = new(buf) std::future<void>();
    *fut = std::async(std::launch::async, [fun]() {
        fun();
    });
}

This version will not produce any heap-allocation related overhead, but it seems very illegal, though I am not sure why in particular.

I am aware that it is UB to use an object before it has been constructed, which is not the case. I am not sure why not calling delete in this case would resolve in UB (for heap allocation it is not UB).

Possible problems that I see:

https://ideone.com/C44cfe

UPDATE

Constructing an object in the static storage directly (as has mentioned IlCapitano in the comments) will block each time a move assignment is called (shared state will be destroyed blocking the thread which has removed last reference to it).

Not calling a destructor will case a leak because of not released references to the shared state.

Upvotes: 6

Views: 950

Answers (2)

Pete Becker
Pete Becker

Reputation: 76498

Calling std::async and ignoring the result sounds like "fire and forget". The simplest way to do that is to not use std::async, but to create a detached thread:

std::thread thr(func, data...);
thr.detach();

Upvotes: 1

Caleth
Caleth

Reputation: 63162

It's undefined behaviour to end the lifetime of a non-trivial object without calling it's destructor, which happens as soon as there is a second call_async invocation.

"heap-allocation related overhead" is a misnomer if the only alternative is undefined behaviour. The future returned by async has to live somewhere.

The updated code has defined behaviour: it waits for the previous invocation to be done before launching the next one.

Upvotes: 3

Related Questions