[2026] C++20 Coroutines Complete Guide | Asynchronous Programming Patterns
이 글의 핵심
C++20 coroutines: co_await, co_yield, promise types, generators, tasks, awaitables, and production patterns with examples.
What are C++20 coroutines? Why do we need them?
Problem Scenario: Callback Hell
Problem: Treating asynchronous operations with callbacks leads to code nesting, which reduces readability. 아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// callback hell
// 실행 예제
async_read_file("config.json", {
auto config = parse_json(content);
async_fetch_url(config.url, {
auto data = parse_response(response);
async_save_db(data, {
if (success) {
std::cout << "Done\n";
}
});
});
});
Solution: Coroutines allow you to write asynchronous tasks like synchronous code. 아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Clean with Coroutines
// 실행 예제
Task<void> process() {
auto content = co_await async_read_file("config.json");
auto config = parse_json(content);
auto response = co_await async_fetch_url(config.url);
auto data = parse_response(response);
bool success = co_await async_save_db(data);
if (success) {
std::cout << "Done\n";
}
}
다음은 mermaid를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
flowchart TD
subgraph callback[Callback style]
c1[async_read_file(callback1)]
c2["callback1: parse_json"]
c3[async_fetch_url(callback2)]
c4["callback2: parse_response"]
c5[async_save_db(callback3)]
end
subgraph coroutine[Coroutine style]
co1["co_await async_read_file"]
co2[parse_json]
co3["co_await async_fetch_url"]
co4[parse_response]
co5["co_await async_save_db"]
end
c1 --> c2 --> c3 --> c4 --> c5
co1 --> co2 --> co3 --> co4 --> co5
index
- Basic keywords: co_await, co_yield, co_return
- Promise Type
- Generator implementation
- Task implementation (asynchronous)
- Awaitable object
- Frequently occurring problems and solutions
- Production Patterns
- Complete example: Asynchronous HTTP client
- Performance considerations
1. default keyword
co_yield: Stop after returning value
아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
Generator<int> counter(int max) {
for (int i = 0; i < max; ++i) {
co_yield i; // return i and abort
}
}
int main() {
auto gen = counter(5);
while (gen.next()) {
std::cout << gen.value() << '\n';
}
// 0 1 2 3 4
}
co_return: Exit after returning the final value
다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
Task<int> compute() {
int result = 42;
co_return result; // end
}
co_await: Await asynchronous operation
다음은 간단한 cpp 코드 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
Task<std::string> fetch_data() {
auto response = co_await async_http_get("https://api.example.com/data");
co_return response;
}
2. Promise Type
What is Promise Type?
A type that defines the behavior of a Coroutine. promise_type must be nested in the return type of the coroutine function.
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct MyCoroutine {
struct promise_type {
// 1. Create Coroutine object
MyCoroutine get_return_object() {
return MyCoroutine{std::coroutine_handle<promise_type>::from_promise(*this)};
}
// 2. Whether there is an initial suspension or not
std::suspend_always initial_suspend() { return {}; } // interruption
// std::suspend_never initial_suspend() { return {}; } // execute immediately
// 3. Final discontinuation or not
std::suspend_always final_suspend() noexcept { return {}; }
// 4. Return processing
void return_void() {}
// void return_value(T value) { this->value = value; }
// 5. Exception handling
void unhandled_exception() {
exception = std::current_exception();
}
// 6. Yield processing (for generator)
std::suspend_always yield_value(T value) {
this->value = value;
return {};
}
T value;
std::exception_ptr exception;
};
std::coroutine_handle<promise_type> handle;
~MyCoroutine() {
if (handle) handle.destroy();
}
};
3. Generator implementation
Complete Generator
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <coroutine>
#include <iostream>
#include <stdexcept>
template<typename T>
struct Generator {
struct promise_type {
T value;
std::exception_ptr exception;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {
exception = std::current_exception();
}
std::suspend_always yield_value(T v) {
value = v;
return {};
}
};
std::coroutine_handle<promise_type> handle;
explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() {
if (handle) handle.destroy();
}
// Copying prohibited
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
// moveable
Generator(Generator&& other) noexcept : handle(other.handle) {
other.handle = nullptr;
}
Generator& operator=(Generator&& other) noexcept {
if (this != &other) {
if (handle) handle.destroy();
handle = other.handle;
other.handle = nullptr;
}
return *this;
}
bool next() {
if (!handle || handle.done()) return false;
handle.resume();
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
return !handle.done();
}
T value() const {
return handle.promise().value;
}
};
// Example usage: Fibonacci
Generator<int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int next = a + b;
a = b;
b = next;
}
}
int main() {
auto fib = fibonacci(10);
while (fib.next()) {
std::cout << fib.value() << ' ';
}
std::cout << '\n';
// 0 1 1 2 3 5 8 13 21 34
}
4. Task implementation (asynchronous)
Complete Task
#include <coroutine>
#include <exception>
#include <iostream>
template<typename T>
struct Task {
struct promise_type {
T value;
std::exception_ptr exception;
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; } // run immediately
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(T v) {
value = v;
}
void unhandled_exception() {
exception = std::current_exception();
}
};
std::coroutine_handle<promise_type> handle;
explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}
~Task() {
if (handle) handle.destroy();
}
Task(const Task&) = delete;
Task& operator=(const Task&) = delete;
Task(Task&& other) noexcept : handle(other.handle) {
other.handle = nullptr;
}
T get() {
if (!handle.done()) {
handle.resume();
}
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
return handle.promise().value;
}
bool done() const {
return handle.done();
}
};
// Example usage
Task<int> async_compute(int x) {
// Asynchronous task simulation
co_return x * x;
}
int main() {
auto task = async_compute(10);
std::cout << "Result: " << task.get() << '\n'; // 100
}
5. Awaitable object
Awaitable interface
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Awaitable {
// 1. Is it completed immediately?
bool await_ready() const noexcept {
return false; // Stop if false
}
// 2. Run on interruption
void await_suspend(std::coroutine_handle<> h) const noexcept {
// Interruption logic (adding tasks to thread pool, etc.)
}
// 3. Return value upon resumption
int await_resume() const noexcept {
return 42;
}
};
Task<int> example() {
int value = co_await Awaitable{};
co_return value;
}
Awaitable in action: Timer
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <coroutine>
#include <chrono>
#include <thread>
struct SleepAwaitable {
std::chrono::milliseconds duration;
bool await_ready() const noexcept {
return duration.count() <= 0;
}
void await_suspend(std::coroutine_handle<> h) const {
std::thread([h, d = duration]() {
std::this_thread::sleep_for(d);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
Task<void> delayed_print() {
std::cout << "Start\n";
co_await SleepAwaitable{std::chrono::seconds(1)};
std::cout << "After 1 second\n";
co_await SleepAwaitable{std::chrono::seconds(2)};
std::cout << "After 3 seconds total\n";
}
6. Frequently occurring problems and solutions
Issue 1: Missing promise_type
Symptom: error: unable to find the promise type for this coroutine.
Cause: No promise_type in return type.
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Incorrect use
struct MyCoroutine {
// promise_type None
};
MyCoroutine func() {
co_return; // Error
}
// ✅ Correct use
struct MyCoroutine {
struct promise_type {
MyCoroutine get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Issue 2: Lifespan Management
Symptoms: Crash, dangling handle. Cause: Coroutine handle not properly destroyed. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Incorrect use
struct Generator {
std::coroutine_handle<promise_type> handle;
// No destructor → memory leak
};
// ✅ Correct use: RAII
struct Generator {
std::coroutine_handle<promise_type> handle;
~Generator() {
if (handle) handle.destroy();
}
// Copying prohibited
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
// moveable
Generator(Generator&& other) noexcept : handle(other.handle) {
other.handle = nullptr;
}
};
Issue 3: Exception handling
Symptom: Coroutine internal exception is ignored. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Incorrect use
struct promise_type {
void unhandled_exception() {
// Do nothing → lose exception
}
};
// ✅ Correct usage: Exception saved and reoccurred
struct promise_type {
std::exception_ptr exception;
void unhandled_exception() {
exception = std::current_exception();
}
};
T get() {
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
return handle.promise().value;
}
Problem 4: Mixing co_await and regular functions
Cause: co_await can only be used within a Coroutine.
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ Incorrect use
void regular_function() {
co_await something(); // Error: not a coroutine
}
// ✅ Correct use: Coroutine functions
Task<void> coroutine_function() {
co_await something(); // OK
}
7. production pattern
Pattern 1: Error handling
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
struct Result {
std::variant<T, std::string> data;
bool has_value() const {
return std::holds_alternative<T>(data);
}
T value() const {
return std::get<T>(data);
}
std::string error() const {
return std::get<std::string>(data);
}
};
Task<Result<std::string>> safe_fetch(const std::string& url) {
try {
auto response = co_await async_http_get(url);
co_return Result<std::string>{response};
} catch (const std::exception& e) {
co_return Result<std::string>{std::string(e.what())};
}
}
Pattern 2: Generator chaining
다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
Generator<int> map(Generator<int> gen, int (*f)(int)) {
while (gen.next()) {
co_yield f(gen.value());
}
}
Generator<int> filter(Generator<int> gen, bool (*pred)(int)) {
while (gen.next()) {
int val = gen.value();
if (pred(val)) {
co_yield val;
}
}
}
Generator<int> range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i;
}
}
int main() {
auto gen = range(0, 10);
auto doubled = map(std::move(gen), { return x * 2; });
auto evens = filter(std::move(doubled), { return x % 2 == 0; });
while (evens.next()) {
std::cout << evens.value() << ' ';
}
// 0 4 8 12 16
}
Pattern 3: Asynchronous timeout
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
Task<std::optional<T>> with_timeout(Task<T> task, std::chrono::milliseconds timeout) {
auto start = std::chrono::steady_clock::now();
while (!task.done()) {
auto elapsed = std::chrono::steady_clock::now() - start;
if (elapsed > timeout) {
co_return std::nullopt; // time out
}
co_await std::suspend_always{};
}
co_return task.get();
}
8. Complete example: Asynchronous file processing
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <coroutine>
#include <fstream>
#include <string>
#include <iostream>
template<typename T>
struct Task {
struct promise_type {
T value;
std::exception_ptr exception;
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(T v) { value = v; }
void unhandled_exception() { exception = std::current_exception(); }
};
std::coroutine_handle<promise_type> handle;
explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}
~Task() { if (handle) handle.destroy(); }
Task(const Task&) = delete;
Task(Task&& other) noexcept : handle(other.handle) {
other.handle = nullptr;
}
T get() {
if (!handle.done()) handle.resume();
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
return handle.promise().value;
}
};
// Asynchronous file reading (simulation)
Task<std::string> async_read_file(const std::string& path) {
std::ifstream file(path);
if (!file) {
throw std::runtime_error("File not found: " + path);
}
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
co_return content;
}
// File processing pipeline
Task<int> process_files() {
try {
auto content1 = co_await async_read_file("file1.txt");
std::cout << "File1 size: " << content1.size() << '\n';
auto content2 = co_await async_read_file("file2.txt");
std::cout << "File2 size: " << content2.size() << '\n';
co_return content1.size() + content2.size();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
co_return -1;
}
}
int main() {
auto task = process_files();
int total_size = task.get();
std::cout << "Total: " << total_size << '\n';
}
9. Performance considerations
Coroutine vs Thread
| Item | Coroutines | thread |
|---|---|---|
| creation cost | Low (hundreds of bytes) | High (number of MB) |
| Context Switching | Fast (function call level) | Slow (kernel intervention) |
| concurrent execution | cooperative (explicit break) | Preemptive (OS Scheduling) |
| Suitable for | I/O standby, Generator | CPU-intensive parallel tasks |
| Summary: Coroutines are more efficient than threads for asynchronous operations with large I/O waits. This is useful on servers handling thousands of concurrent connections. |
organize
| concept | Description |
|---|---|
| Coroutine | abort/resumable functions |
| co_yield | Break after returning value |
| co_return | Exit after returning final value |
| co_await | Asynchronous task await |
| Promise Type | Coroutine behavior definition |
| Generator | Lazy evaluation sequence |
| Task | asynchronous operation |
| C++20 Coroutines allow you to write asynchronous code synchronously, greatly improving readability and maintainability. |
FAQ
Q1: Coroutine vs async/await (different languages)?
A: Coroutines in C++ are low-level mechanisms. Although you must implement the Promise Type yourself, it is flexible enough. async/await in C#/JavaScript is a high-level feature built into the language.
Q2: Generator vs std::ranges?
A: Generator generates values one by one with lazy evaluation. std::ranges is a view of an existing container, and Generator dynamically creates values.
Q3: Does co_await always abort?
A: If await_ready() returns true, call await_resume() immediately without stopping. Work that has already been completed can proceed without interruption.
Q4: Does Coroutine create threads?
A: No. The coroutine itself runs on a single thread. This can be combined with multithreading by passing work to a thread pool in await_suspend.
Q5: What is compiler support?
A:
- GCC 10+: Fully supported (
-fcoroutines) - Clang 14+: Fully supported
- MSVC 2019+: Fully supported (
/await)
Q6: What are the Coroutines learning resources?
A:
- cppreference - Coroutines
- “C++20: The Complete Guide” by Nicolai Josuttis
- Lewis Baker’s Coroutine Theory One-line summary: C++20 Coroutines allow you to write clean asynchronous code. Next, you might want to read consteval.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++20 Concepts Complete Guide | A new era of template constraints
- Complete Guide to C++20 Modules | Beyond header files
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented? Use this checklist to reduce mistakes and improve code quality.