cpp-coroutines

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

C++20 Coroutines

C++20协程

Purpose

用途

Guide agents through C++20 coroutine mechanics:
co_await
,
co_yield
,
co_return
, implementing the required
promise_type
, understanding coroutine frame memory layout, debugging suspended coroutines in GDB, and reducing frame allocation overhead.
引导使用者掌握C++20协程机制:
co_await
co_yield
co_return
、实现所需的
promise_type
、理解协程帧内存布局、在GDB中调试挂起的协程,以及降低帧分配开销。

Triggers

触发场景

  • "How do co_await, co_yield, and co_return work?"
  • "How do I implement promise_type for a coroutine?"
  • "How does a coroutine suspend and resume?"
  • "How do I debug a suspended coroutine in GDB?"
  • "How much memory does a coroutine frame use?"
  • "How do I write a generator with co_yield?"
  • "co_await、co_yield和co_return的工作原理是什么?"
  • "如何为协程实现promise_type?"
  • "协程如何挂起与恢复?"
  • "如何在GDB中调试挂起的协程?"
  • "协程帧占用多少内存?"
  • "如何用co_yield编写生成器?"

Workflow

工作流程

1. The three coroutine keywords

1. 三个协程关键字

cpp
// co_return — return a value and end the coroutine
co_return value;

// co_yield — produce a value, suspend, resume later
co_yield value;

// co_await — suspend until an awaitable completes
auto result = co_await some_awaitable;
A function is a coroutine if it contains any of these three keywords. Its return type must be a coroutine type with a
promise_type
.
cpp
// co_return — 返回值并结束协程
co_return value;

// co_yield — 生成值,挂起协程,后续可恢复
co_yield value;

// co_await — 挂起协程直到awaitable完成
auto result = co_await some_awaitable;
如果函数包含这三个关键字中的任意一个,它就是协程。其返回类型必须是带有
promise_type
的协程类型。

2. Minimal coroutine type — Task

2. 最小协程类型——Task

cpp
#include <coroutine>
#include <stdexcept>
#include <optional>

template <typename T>
struct Task {
    struct promise_type {
        std::optional<T> value;
        std::exception_ptr exception;

        Task get_return_object() {
            return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
        }

        std::suspend_always initial_suspend() { return {}; }  // lazy start
        std::suspend_always final_suspend() noexcept { return {}; }

        void return_value(T v) { value = std::move(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(Task&&) = default;
    Task& operator=(Task&&) = default;

    ~Task() { if (handle) handle.destroy(); }

    T get() {
        handle.resume();                      // resume to completion
        if (handle.promise().exception)
            std::rethrow_exception(handle.promise().exception);
        return std::move(*handle.promise().value);
    }
};

// Usage
Task<int> compute() {
    co_return 42;
}

int main() {
    auto task = compute();
    int result = task.get();   // 42
}
cpp
#include <coroutine>
#include <stdexcept>
#include <optional>

template <typename T>
struct Task {
    struct promise_type {
        std::optional<T> value;
        std::exception_ptr exception;

        Task get_return_object() {
            return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
        }

        std::suspend_always initial_suspend() { return {}; }  // 延迟启动
        std::suspend_always final_suspend() noexcept { return {}; }

        void return_value(T v) { value = std::move(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(Task&&) = default;
    Task& operator=(Task&&) = default;

    ~Task() { if (handle) handle.destroy(); }

    T get() {
        handle.resume();                      // 恢复协程至完成
        if (handle.promise().exception)
            std::rethrow_exception(handle.promise().exception);
        return std::move(*handle.promise().value);
    }
};

// 使用示例
Task<int> compute() {
    co_return 42;
}

int main() {
    auto task = compute();
    int result = task.get();   // 结果为42
}

3. Generator with co_yield

3. 基于co_yield的Generator

cpp
template <typename T>
struct Generator {
    struct promise_type {
        T current_value;

        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() { throw; }

        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};                     // suspend after yielding
        }
    };

    std::coroutine_handle<promise_type> handle;

    explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    struct iterator {
        std::coroutine_handle<promise_type> handle;
        bool done;

        iterator& operator++() {
            handle.resume();
            done = handle.done();
            return *this;
        }
        T operator*() const { return handle.promise().current_value; }
        bool operator!=(std::default_sentinel_t) const { return !done; }
    };

    iterator begin() {
        handle.resume();                   // advance to first yield
        return {handle, handle.done()};
    }
    std::default_sentinel_t end() { return {}; }
};

// Usage
Generator<int> iota(int start, int end) {
    for (int i = start; i < end; ++i)
        co_yield i;
}

for (int x : iota(0, 5)) {
    std::cout << x << ' ';   // 0 1 2 3 4
}
cpp
template <typename T>
struct Generator {
    struct promise_type {
        T current_value;

        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() { throw; }

        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};                     // 生成值后挂起
        }
    };

    std::coroutine_handle<promise_type> handle;

    explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    struct iterator {
        std::coroutine_handle<promise_type> handle;
        bool done;

        iterator& operator++() {
            handle.resume();
            done = handle.done();
            return *this;
        }
        T operator*() const { return handle.promise().current_value; }
        bool operator!=(std::default_sentinel_t) const { return !done; }
    };

    iterator begin() {
        handle.resume();                   // 推进到第一个生成值
        return {handle, handle.done()};
    }
    std::default_sentinel_t end() { return {}; }
};

// 使用示例
Generator<int> iota(int start, int end) {
    for (int i = start; i < end; ++i)
        co_yield i;
}

for (int x : iota(0, 5)) {
    std::cout << x << ' ';   // 输出:0 1 2 3 4
}

4. Awaitable — custom co_await target

4. Awaitable——自定义co_await目标

cpp
// An awaitable has three methods:
// await_ready() — true means don't suspend
// await_suspend(handle) — suspend: store handle, schedule resume
// await_resume() — return value of co_await expression

struct TimerAwaitable {
    int delay_ms;

    bool await_ready() const noexcept { return delay_ms <= 0; }

    void await_suspend(std::coroutine_handle<> h) {
        // Schedule h.resume() to be called after delay
        std::thread([h, this]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
            h.resume();
        }).detach();
    }

    void await_resume() const noexcept {}  // no return value
};

// suspend_always and suspend_never are built-in awaitables
std::suspend_always{};   // always suspends
std::suspend_never{};    // never suspends (no-op)
cpp
// 一个Awaitable需包含三个方法:
// await_ready() — 返回true表示不挂起
// await_suspend(handle) — 挂起:存储handle,调度恢复
// await_resume() — 返回co_await表达式的结果

struct TimerAwaitable {
    int delay_ms;

    bool await_ready() const noexcept { return delay_ms <= 0; }

    void await_suspend(std::coroutine_handle<> h) {
        // 调度h.resume()在延迟后执行
        std::thread([h, this]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
            h.resume();
        }).detach();
    }

    void await_resume() const noexcept {}  // 无返回值
};

// suspend_always和suspend_never是内置Awaitable
std::suspend_always{};   // 始终挂起
std::suspend_never{};    // 从不挂起(空操作)

5. Coroutine frame layout and memory

5. 协程帧布局与内存

The compiler allocates a coroutine frame (heap object) containing:
  • Local variables that live across suspension points
  • The promise object
  • The current suspension state (where to resume)
  • A pointer to the resumption/destruction functions
cpp
// Inspect frame size with Compiler Explorer (godbolt.org)
// Compile with: g++ -std=c++20 -O2 -S
// Look for: operator new call size in the generated asm
// Or: clang -std=c++20 -O2 -emit-llvm -S | grep "coro.size"

// Reduce frame size:
// 1. Don't keep large objects alive across co_await
struct Bad {
    std::vector<char> large_buf;   // whole vector lives in frame
    co_return large_buf.size();    // large_buf crosses suspension
};

// 2. Move data out before suspending
std::vector<char> buf = get_data();
size_t sz = buf.size();            // capture only what's needed
buf.clear();                       // release before suspension
co_await next_event;
// sz still valid; buf released
编译器会分配一个协程帧(堆对象),其中包含:
  • 跨挂起点存活的局部变量
  • promise对象
  • 当前挂起状态(恢复位置)
  • 恢复/销毁函数的指针
cpp
// 使用Compiler Explorer(godbolt.org)检查帧大小
// 编译命令:g++ -std=c++20 -O2 -S
// 在生成的汇编中查找:operator new调用的大小
// 或者:clang -std=c++20 -O2 -emit-llvm -S | grep "coro.size"

// 减小帧大小:
// 1. 不要让大型对象在co_await期间保持存活
struct Bad {
    std::vector<char> large_buf;   // 整个vector存放在帧中
    co_return large_buf.size();    // large_buf跨越挂起点
};

// 2. 在挂起前转移数据
std::vector<char> buf = get_data();
size_t sz = buf.size();            // 仅保留所需数据
buf.clear();                       // 挂起前释放内存
co_await next_event;
// sz仍然有效;buf已释放

6. Debugging suspended coroutines in GDB

6. 在GDB中调试挂起的协程

bash
undefined
bash
undefined

Coroutines appear as regular stack frames after resume()

调用resume()后,协程会显示为常规栈帧

To inspect a suspended coroutine:

要检查挂起的协程:

(gdb) info locals
(gdb) info locals

Look for coroutine_handle variables

查找coroutine_handle变量

Print the promise object

打印promise对象

(gdb) p (promise_type)(handle._handle)
(gdb) p (promise_type)(handle._handle)

GDB 14+ has coroutine-specific support

GDB 14+版本支持协程专属功能

(gdb) info coroutines # GCC coroutine support (experimental)
(gdb) info coroutines # GCC协程支持(实验性)

Step through coroutine execution

单步执行协程

(gdb) step # enters co_await implementation (gdb) finish # returns from coroutine frame function (gdb) next # step over suspension point
(gdb) step # 进入co_await实现 (gdb) finish # 从协程帧函数返回 (gdb) next # 跳过挂起点

View all threads (coroutines running on thread pool)

查看所有线程(运行在线程池中的协程)

(gdb) info threads (gdb) thread 2 (gdb) bt
undefined
(gdb) info threads (gdb) thread 2 (gdb) bt
undefined

7. Common pitfalls

7. 常见陷阱

IssueCauseFix
co_await
in a non-coroutine
Function missing coroutine return typeChange return type to a coroutine type
Dangling handle after
co_return
Using handle after coroutine finishesCheck
handle.done()
before resume
Double-resumeResuming an already-resumed coroutineTrack state; only resume when suspended
Coroutine frame never freedForgot
handle.destroy()
Use RAII wrapper (Task, Generator)
Heap allocation overheadNew frame per coroutine callEnable HALO (Heap Allocation eLision Optimization) with
-O2
Recursive co_await depthStack overflow from deep chainsUse
std::coroutine_handle<>
tail-call pattern
问题原因修复方案
非协程中使用
co_await
函数缺少协程返回类型将返回类型改为协程类型
co_return
后出现悬空handle
协程结束后仍使用handle恢复前检查
handle.done()
重复恢复恢复已恢复的协程跟踪状态;仅在挂起时恢复
协程帧从未释放忘记调用
handle.destroy()
使用RAII包装器(Task、Generator)
堆分配开销每次协程调用都会创建新帧使用
-O2
启用HALO(堆分配消除优化)
递归co_await深度过深深层调用链导致栈溢出使用
std::coroutine_handle<>
尾调用模式

Related skills

相关技能

  • Use
    skills/compilers/cpp-templates
    for other advanced C++20 features
  • Use
    skills/rust/rust-async-internals
    for Rust's equivalent Future/Poll model
  • Use
    skills/debuggers/gdb
    for GDB session management
  • 如需了解其他C++20高级特性,请使用
    skills/compilers/cpp-templates
  • 如需了解Rust对应的Future/Poll模型,请使用
    skills/rust/rust-async-internals
  • 如需进行GDB会话管理,请使用
    skills/debuggers/gdb