Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • calling a constructor on one object multiple times
  • race condition when modifying the state (inner std::promise I suppose)

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.

like image 361
Sergey Kolesnik Avatar asked Apr 01 '21 09:04

Sergey Kolesnik


People also ask

Should I use std async?

So if you want to make sure that the work is done asynchronously, use std::launch::async . @user2485710 it needs to block when you retrieve the result, if you need the result in the launching thread. It cannot use the result if the result is not ready. So if you go to get the result, you have to wait until it is ready.

Does STD async block?

If the std::async runs on an other thread, the async call will not return until printThreadId is done working. That is, std::async will block.

What does STD async do?

std::async. Calls fn (with args as arguments) at some point, returning without waiting for the execution of fn to complete. The value returned by fn can be accessed through the future object returned (by calling its member future::get ).

Does STD async use thread pool?

How does std::launch::async Work in Different Implementations? For now, we know that if no policy is specified, then std::async launches a callable function in a separate thread. However, the C++ standard does not specify whether the thread is a new one or reused from a thread pool.

Is it bad to block async call?

Blocking an async call can lead to the same negative impact, but it won’t deadlock the first thread. As a result, you should not block an async call until it is actually needed. When blocking a call to an async method, you transform the asynchronous code into synchronous code.

What are the problems with async calls in Java?

Another problem with async calls is that they are often very difficult to read. The first problem with blocking async calls is that it turns asynchronous code into synchronous code. This can lead to unexpected blocking of context threads and deadlocks.

Should you use async calls in your application?

Moreover, an async call would require the use of a thread pool, a scheduler, and a complete synchronous programming system. While async calls are useful for entertainment and preventing mobile applications from freezing, they’re not very efficient. They use more resources than normal application usage, causing the app to slow down.

What is the new runtime in async?

The new runtime relieves you of these concerns and allows you to do the blocking operation directly inside the async function: The runtime measures the time it takes to perform the blocking operation and if it takes a while, a new thread is automatically spawned and replaces the old executor thread.


2 Answers

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.

like image 50
Caleth Avatar answered Oct 13 '22 00:10

Caleth


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();
like image 40
Pete Becker Avatar answered Oct 13 '22 01:10

Pete Becker