What are Coroutines?
When many people see the new feature coroutines introduced in C++20, they might be puzzled and unsure how to use it.
Are coroutines the same as multithreading? The answer is: No!
Coroutines are not a multithreading technology but a method for performing non-blocking asynchronous operations within a single thread. Coroutines allow efficient task switching within a thread without the overhead of context switching and synchronization associated with multithreading. Understanding this point makes it much clearer when looking at the code.
Differences Between Coroutines and Multithreading
Coroutines:
- Cooperative multitasking within a single thread: Coroutines run within the same thread, achieving asynchronous operations by yielding and resuming control.
- Lightweight: Context switching in coroutines is much lighter because it doesn’t involve OS-level context switching.
- Yielding control: Coroutines can yield control when they need to wait, allowing other coroutines to run.
- Simplified asynchronous programming: Coroutines make asynchronous code look like synchronous code, simplifying the complexity of asynchronous programming.
Multithreading:
- Parallel execution on multiple threads: Multithreading allows code to run in parallel on multiple processor cores, suitable for CPU-intensive tasks.
- High context-switching overhead: Context switching in threads involves OS scheduling, which has higher overhead.
- Need for synchronization mechanisms: Threads need locks or other synchronization mechanisms to protect shared data and avoid race conditions.
- Complexity: Multithreading programming is usually more complex, prone to issues like deadlocks and race conditions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <coroutine>
#include <thread>
struct ReturnObject {
struct promise_type {
ReturnObject get_return_object() {
return {
.h = std::coroutine_handle<promise_type>::from_promise(*this)
};
}
std::suspend_never initial_suspend() {
return {};
}
std::suspend_never final_suspend() noexcept {
return {};
}
void unhandled_exception() {
std::terminate();
}
void return_void() {}
};
std::coroutine_handle<promise_type> h;
};
ReturnObject counter() {
std::cout << "enter counter: " << std::endl;
for (int i = 0; i < 3; ++i) {
std::cout << "counter: " << i << std::endl;
co_await std::suspend_always{};
std::cout << "thread ID:[" << std::this_thread::get_id() << "]" << std::endl;
std::cout << "counter after suspend_always: " << i << std::endl;
}
std::cout << "leave counter: " << std::endl;
}
int main() {
std::cout << "main thread ID:[" << std::this_thread::get_id() <<"]" << std::endl;
std::cout << "-----------" << std::endl;
auto c = counter();
for (int i = 0; i < 3; ++i) {
std::cout << "main loop i: " << i << std::endl;
c.h();
std::cout << "-----------" << std::endl;
}
return 0;
}
Note
1
2
3
4
5
ReturnObject get_return_object() {
return {
.h = std::coroutine_handle<promise_type>::from_promise(*this)
};
}
The .h file here uses designated initializer.
Execution Result:
- You can see that the main thread is executing from start to finish.
- The execution sequence is counter’s loop -> main’s loop -> counter’s loop…
Use Cases
Coroutines are suitable for the following scenarios:
- Asynchronous I/O operations: Such as network requests, file reading/writing, etc., where you need to wait for I/O operations to complete without blocking the main thread.
- Event-driven applications: Such as GUI applications, where you need to keep the interface responsive while handling user events.
- Game development: For handling asynchronous events like resource loading, network communication, etc.
- Cooperative multitasking: For efficiently switching tasks within a single thread, such as data stream processing, asynchronous computations, etc.
If you’re still feeling a bit fuzzy about coroutines, don’t worry. It’s common to feel this way initially. Just remember the most important point: Coroutines are not a multithreading technology.
The rest will become clear with more reading and practice.

