Task Based Programming Over Thread Base
Source: Dev.to
Prefer Task‑Based Programming to Thread‑Based
If you want to run something asynchronously you have two basic choices: std::thread and std::async.
int doAsyncWork();
std::thread th(doAsyncWork); // thread‑basedUsing std::async (recommended)
auto future = std::async(doAsyncWork);std::async(doAsyncWork) creates a task.
The task is superior because it returns a std::future that can be used to synchronize the program. The future provides a get() function to retrieve the result or re‑throw any exception thrown by the work function. Tasks also offer a higher level of abstraction.
What is a Thread?
- Hardware threads – the physical execution units provided by the CPU. Modern CPUs may expose one or more hardware threads per core.
- Software threads (OS or system threads) – abstractions managed by the operating system, scheduled onto hardware threads.
std::threadobjects are handles to underlying software threads.
Software threads are a limited resource. Creating more threads than the system can provide throws a std::system_error (even from a noexcept function).
Even when you don’t exhaust the thread pool, oversubscription can occur: more ready‑to‑run software threads than available hardware threads, leading to frequent context switches and increased overhead.
Why Prefer std::async?
std::async delegates the management of thread creation and scheduling to the implementation. A task created with std::async typically avoids the oversubscription problem because it does not guarantee the creation of a new software thread. Instead, the scheduler may run the function on an existing thread when the result is needed.
When the default launch policy is used, the implementation may choose between asynchronous execution (new thread) or deferred execution (same thread).
Launch Policies
| Policy | Behaviour |
|---|---|
std::launch::async | The function is guaranteed to run on a new thread. |
std::launch::deferred | The function does not run immediately. It runs only when future.get() or future.wait() is called, executing on the calling thread. |
Default (std::launch::async | std::launch::deferred) | The implementation decides whether to run the task asynchronously (new thread) or defer it (same thread, lazy execution). |
Using std::async with an appropriate launch policy lets you write clearer, safer asynchronous code without manually handling thread lifetimes and oversubscription concerns.