C++ 协程

C++20 引入了协程(Coroutines),协程是一种可以在执行过程中暂停和恢复的函数,允许在特定点挂起(suspend)执行,并将控制权交还给调用者,稍后可以从暂停点恢复执行。

应用场景

与传统的函数调用不同,协程可以在不丢失上下文的情况下暂停,适合以下场景:

1.异步编程:例如处理 I/O 操作(如网络请求、文件读写)时,避免阻塞线程。

2.生成器:逐步生成数据序列,适合处理大数据流或惰性求值。

3.协作式多任务:多个任务协作运行,共享线程资源。

关键字

C++ 的协程是通过编译器支持的底层机制实现的,主要依赖三个关键字:

1.co_await:暂停协程并等待某个异步操作完成。

2.co_yield:暂停协程并向调用者返回一个值(用于生成器)。

3.co_return:结束协程并返回结果。

关键组件

C++ 协程的实现依赖于几个关键组件,这些组件共同定义了协程的行为:

1.Promise 对象(promise_type):每个协程都有一个关联的 promise_type,它定义了协程的行为和返回值。promise_type 是一个类,通常包含以下方法:

get_return_object():定义协程的返回值对象(通常是一个协程句柄或自定义类型)。

initial_suspend() :决定协程在启动时是否立即挂起(返回 std::suspend_alwaysstd::suspend_never)。

final_suspend():决定协程在结束时是否挂起。

return_void()return_value(T):处理协程的返回值。

yield_value(T) :处理通过 co_yield 返回的值(生成器场景)。

unhandled_exception():处理协程中未捕获的异常。

2.协程句柄(std::coroutine_handle):std::coroutine_handle 是一个模板类,用于管理协程的状态和生命周期。它可以:恢复协程(resume()),检查协程是否完成(done()),销毁协程(destroy())。

3.协程框架:协程的执行需要一个框架来管理暂停和恢复。C++20 标准库没有提供内置的协程调度器,因此开发者通常需要:手动管理协程句柄,使用第三方库(如 liburingboost::asio)提供的事件循环或调度器。

4.暂停点:暂停点由 co_awaitco_yield 定义:

co_await expr:暂停协程,等待表达式 expr(通常是一个等待器,awaitable)完成。

co_yield expr:暂停协程并向调用者返回一个值。

暂停时,协程的状态(包括局部变量)被保存在堆上,称为协程帧(coroutine frame)。

工作原理

C++ 协程的实现依赖编译器将协程函数转换为状态机(state machine)。其工作原理如下:

1.函数转换 :当编译器遇到 co_awaitco_yieldco_return 时,它将协程函数拆分为多个状态,每个状态对应一个暂停点或结束点。

2.协程帧分配:协程的局部变量和状态存储在堆上的协程帧中,确保暂停后状态不丢失。

3.状态机执行:协程在暂停点挂起时,返回控制权给调用者;恢复时,从暂停点继续执行。

4.生命周期管理 :协程帧的分配和销毁由 promise_typestd::coroutine_handle 管理。

示例

生成器示例(使用 co_yield

这个示例实现一个生成整数序列的协程:

cpp 复制代码
#include <iostream>
#include <coroutine>
#include <optional>

// 生成器类型
template<typename T>
struct Generator {
    struct promise_type {
        std::optional<T> 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() {}
        std::suspend_always yield_value(T val) {
            value = val;
            return {};
        }
        void unhandled_exception() {}
    };

    struct Iterator {
        std::coroutine_handle<promise_type> coro;
        bool done;

        Iterator& operator++() {
            if (!coro.done()) {
                coro.resume();
                done = coro.done();
            }
            return *this;
        }

        T operator*() const {
            return *coro.promise().value;
        }

        bool operator!=(const Iterator& other) const {
            return !done;
        }
    };

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

    Iterator begin() {
        coro.resume(); // 运行到第一个 yield
        return {coro, coro.done()};
    }

    Iterator end() {
        return {coro, true};
    }
};

// 生成 1 到 n 的序列
Generator<int> generate_sequence(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i;
    }
}

int main() {
    for (int val : generate_sequence(5)) {
        std::cout << val << " "; // 输出: 1 2 3 4 5
    }
    std::cout << "\n";
    return 0;
}

说明:Generator 是一个自定义协程类型,存储通过 co_yield 返回的值。promise_typeyield_value 方法将每次 co_yield 的值保存到 values 向量中。main 函数调用生成器并打印结果。

异步示例(使用 co_await

以下是一个模拟异步任务的协程,假设有一个简单的等待器:

cpp 复制代码
#include <iostream>
#include <coroutine>
#include <chrono>
#include <thread>

// 模拟的等待器
struct Awaiter {
    bool await_ready() { return false; } // 总是暂停
    void await_suspend(std::coroutine_handle<> h) {
        // 模拟异步操作(如 I/O)
        std::thread([h]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            h.resume(); // 恢复协程
        }).detach();
    }
    void await_resume() {}
};

// 异步任务
struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

Task async_task(int id) {
    std::cout << "Task " << id << " started\n";
    co_await Awaiter{}; // 模拟异步等待
    std::cout << "Task " << id << " completed\n";
}

int main() {
    // 启动 5 个异步任务(避免 10000 个以简化输出)
    std::vector<Task> tasks;
    for (int i = 0; i < 5; ++i) {
        tasks.push_back(async_task(i));
    }
    // 等待所有任务完成(简单模拟)
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    return 0;
}

说明:Awaiter 模拟一个异步操作(如网络 I/O),在 100ms 后恢复协程。co_await Awaiter{} 暂停协程,直到异步操作完成。main 启动多个异步任务,实际场景中需要事件循环来管理。

协程的优缺点

优点

1.高效性:协程在用户态管理暂停和恢复,避免了线程切换的开销。

2.灵活性:C++ 协程是底层机制,允许开发者自定义调度和行为。

3.异步编程简化:与回调或 std::future 相比,协程代码更直观,类似同步代码。

4.生成器支持:适合处理流式数据或惰性求值。

缺点

1.复杂性:需要手动定义 promise_type 和调度逻辑,学习曲线陡峭。

2.标准库支持不足:C++20 没有内置事件循环或调度器,需依赖第三方库。

3.内存管理:协程帧在堆上分配,可能增加内存开销。

4.调试困难:状态机转换和协程帧管理可能导致调试复杂。

性能优化

避免在协程中分配过多内存,合理设计暂停点。

相关推荐
t_hj2 分钟前
Scrapy
前端·数据库·scrapy
小唐快跑5 分钟前
🚀 2025 VS Code前端开发环境搭建指南:从入门到精通(含插件推荐+配置代码)
前端
bug_kada5 分钟前
全家桶开发之Zustand:轻量级状态管理
前端·react.js
用泥种荷花8 分钟前
【记一忘三二】脚手架学习
前端
庄毕楠29 分钟前
【Chrome】下载chromedriver的地址
前端·chrome
大猫会长29 分钟前
关闭chrome自带的跨域限制,简化本地开发
前端·chrome
excel35 分钟前
JavaScript 中使用 Set 对数组去重并排序的简洁示例
前端
不断努力的根号七2 小时前
qt框架,使用webEngine如何调试前端
开发语言·前端·qt
伍哥的传说3 小时前
React性能优化终极指南:memo、useCallback、useMemo全解析
前端·react.js·性能优化·usecallback·usememo·react.memo·react devtools