跳转到内容

C++20 协程

来自代码酷

C++20协程[编辑 | 编辑源代码]

协程是C++20引入的一项重大语言特性,它允许函数在执行过程中暂停和恢复,从而简化异步编程和惰性计算的实现。本节将详细介绍协程的概念、语法和实际应用。

基本概念[编辑 | 编辑源代码]

协程(Coroutine)是一种可以暂停执行并在之后恢复的函数。与普通函数不同,协程具有以下特点:

  • 可以在任意点暂停(使用`co_yield`或`co_await`)
  • 保持局部变量状态
  • 通过特定机制恢复执行

C++20协程是无栈协程(stackless coroutine),意味着它们依赖编译器生成状态机而非运行时栈切换。

核心组件[编辑 | 编辑源代码]

C++20协程涉及以下关键组件:

组件 描述
`co_await` 暂停协程直到等待的操作完成
`co_yield` 暂停并返回一个值(用于生成器)
`co_return` 结束协程并返回最终结果
Promise对象 控制协程行为(创建/销毁/返回值处理)
Coroutine句柄 用于恢复协程的外部接口

基本示例:生成器[编辑 | 编辑源代码]

以下是一个简单的生成器协程示例,它生成斐波那契数列:

#include <coroutine>
#include <iostream>

// 生成器类型定义
template<typename T>
struct Generator {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    
    struct promise_type {
        T current_value;
        
        Generator get_return_object() {
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
    };
    
    handle_type h;
    
    explicit Generator(handle_type h_) : h(h_) {}
    ~Generator() { if (h) h.destroy(); }
    
    T operator()() {
        h.resume();
        return h.promise().current_value;
    }
};

// 斐波那契生成器协程
Generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;
        auto next = a + b;
        a = b;
        b = next;
    }
}

int main() {
    auto gen = fibonacci();
    for (int i = 0; i < 10; ++i) {
        std::cout << gen() << " ";
    }
    // 输出: 0 1 1 2 3 5 8 13 21 34
}

协程状态机[编辑 | 编辑源代码]

编译器会将协程转换为状态机。以下mermaid图展示了基本流程:

stateDiagram [*] --> 创建 创建 --> 暂停: initial_suspend 暂停 --> 执行: resume() 执行 --> 暂停: co_yield/co_await 执行 --> 结束: co_return 结束 --> [*]

异步IO示例[编辑 | 编辑源代码]

协程特别适合异步IO操作。以下示例展示异步读取:

#include <coroutine>
#include <iostream>
#include <future>

struct AsyncTask {
    struct promise_type {
        std::future<int> fut;
        
        AsyncTask get_return_object() {
            return AsyncTask(std::move(fut));
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_value(int value) { fut.set_value(value); }
        void unhandled_exception() { std::terminate(); }
    };
    
    std::future<int> fut;
    
    explicit AsyncTask(std::future<int> f) : fut(std::move(f)) {}
};

AsyncTask asyncOperation() {
    int result = co_await std::async([]{
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return 42;
    });
    co_return result;
}

int main() {
    auto task = asyncOperation();
    std::cout << "结果: " << task.fut.get() << "\n"; // 输出: 结果: 42
}

协程与线程比较[编辑 | 编辑源代码]

特性 协程 线程
切换开销 极低(通常1-10ns) 较高(通常1-10μs)
内存占用 通常几百字节 通常几MB(包括栈)
并发模型 协作式 抢占式
数据共享 无竞争条件(单线程) 需要同步

数学基础[编辑 | 编辑源代码]

协程可以看作是一个状态机,其状态转移可以表示为: Sn+1=f(Sn,In) 其中:

  • Sn 是第n次暂停时的状态
  • In 是第n次恢复时的输入
  • f 是协程体函数

性能考虑[编辑 | 编辑源代码]

使用协程时需注意:

  • 协程帧分配通常发生在堆上
  • 小协程可能产生内存碎片
  • 编译器优化程度影响最终性能

实际应用场景[编辑 | 编辑源代码]

1. 游戏开发:异步资源加载 2. 网络服务:高并发连接处理 3. UI编程:保持响应性的长时间操作 4. 数据流处理:惰性求值管道

限制与注意事项[编辑 | 编辑源代码]

  • 协程不能使用可变参数
  • 协程不能是constexpr函数
  • 协程不能返回auto或概念约束类型
  • 需要编译器支持(GCC 10+, Clang 5+, MSVC 19.28+)

进阶主题[编辑 | 编辑源代码]

  • 自定义分配器优化协程帧分配
  • 协程与异常处理的交互
  • 协程调试技术
  • 与其他语言协程的互操作

通过掌握C++20协程,开发者可以编写更高效、更易维护的异步代码,同时保持代码的直观性和可读性。