Asynchronous Programming in C++: Complete Guide
Modern applications require high performance and responsiveness. Asynchronous programming in C++ allows you to perform long-running operations (I/O, network requests, complex computations) without blocking the main thread. In this guide, we will explore key C++ mechanisms for asynchronous work: from classic std::thread and std::async to modern C++20 coroutines. You will learn how to write efficient and safe asynchronous code.
1. Basics: std::thread and std::async
Let's start with the basic tools. std::thread creates a new thread of execution but requires manual management and synchronization. A more convenient option is std::async, which runs a function asynchronously and returns a std::future to retrieve the result.
#include <iostream>#include <future>#include <chrono>
int long_running_task(int input) { std::this_thread::sleep_for(std::chrono::seconds(2)); return input * 10;}
int main() { std::cout << "Starting asynchronous task..."; std::future<int> result = std::async(std::launch::async, long_running_task, 5); // Do something useful while the task is running std::cout << "Main thread continues working..."; // Get the result (blocks if the task is not yet complete) int value = result.get(); std::cout << "Result: " << value << std::endl; return 0;}Key points:
- std::launch::async — guarantees execution in a separate thread.
- std::launch::deferred — defers execution until
get()is called. - The
get()method blocks the thread until the result is available.
2. Advanced Work: std::promise and std::future
When you need to pass a result from one thread to another, use the pair std::promise and std::future. The promise sets the value, and the future retrieves it. This is especially useful for thread pooling or task chains.
#include <iostream>#include <future>#include <thread>
void worker(std::promise<int> prom) { try { int result = 42; // Complex computations prom.set_value(result); } catch (...) { prom.set_exception(std::current_exception()); }}
int main() { std::promise<int> prom; std::future<int> fut = prom.get_future(); std::thread t(worker, std::move(prom)); // Get the result int value = fut.get(); std::cout << "Received value: " << value << std::endl; t.join(); return 0;}Important features:
promisecannot be copied, only moved.- Exceptions are passed via
set_exception. futurecan only be used once (single retrieval).
3. C++20 Coroutines: Modern Approach
Coroutines are a powerful tool added in C++20. They allow suspending and resuming function execution without blocking the thread. This is ideal for asynchronous I/O operations and generators.
#include <iostream>#include <coroutine>#include <future>
struct SimpleTask { struct promise_type { SimpleTask get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() { std::exit(1); } };};
SimpleTask async_operation() { std::cout << "Coroutine start";