当前位置: 首页 > news >正文

协程 Coroutine

协程是 C++20 引入的新特性。

文章目录

  • 基本概念
      • std::coroutine_handle
      • promise 类型
      • co_yield
    • 基本用法
  • 优势
      • 异步 TCP
      • co_await

基本概念

协程(Coroutine)是一种比线程更加轻量级的并发编程模型。协程的调度由程序员手动控制。

异步不是并行,但是不阻塞~

协程的上下文切换是在用户态完成的,不需要陷入内核,因此切换速度非常快。
当协程遇到 co_await 时,它会保存当前的上下文信息,然后让出执行权,待异步操作完成后再恢复上下文继续执行。切换开销非常小,几乎可以忽略不计。

  • 如果你在做异步编程(如网络 I/O、文件 I/O),用 co_await

  • 如果你在实现生成器(如迭代器、流式数据处理),用 co_yield

std::coroutine_handle

协程的句柄,一个模板类——对协程进行控制与管理。

std::coroutine_handle<promise_type>;

主要方法:

  • resume():用于让协程恢复执行。
    当协程处于挂起状态时,调用 resume() 方法,协程就会从挂起的位置接着执行。

  • done():用于检查协程是否已经执行完毕。
    若协程执行完毕,done() 方法会返回 true;反之,则返回 false。

  • destroy():用于销毁协程,释放协程占用的资源。

  • promise():返回与协程关联的 promise 对象(struct promise_type),通过这个对象可以访问协程的状态和数据。

promise 类型

promise_type 对象定义了协程的行为和状态管理方式。

promise_type 结构体中的部分方法名字是固定的,协程框架会依据这些固定的方法名来调用相应的逻辑

固定的方法名:

  • get_return_object 创建并返回协程的返回对象
  • yield_value 当协程里使用 【co_yield】 语句时,会调用此方法。它负责处理 co_yield 后面的值,并通过返回值决定协程是否要挂起。
  • initial_suspend 在协程启动时会调用此方法,决定了协程在开始执行时是否要立即挂起。
    返回类型】有 std::suspend_always(表示协程开始就挂起,控制权交还给调用者)和 std::suspend_never(表示协程开始就执行,co_yield 后接着执行)
  • final_suspend 协程即将结束时会调用该方法,决定了协程在结束时是否要挂起。
    返回同 initial_suspend
  • unhandled_exception 当协程内部抛出未被内部捕获的异常时,会调用这个方法来处理异常。
  • return_voidreturn_value 处理协程返回值。

co_yield

co_yield 用于在协程中产生一个值,然后暂停协程的执行,将控制权交还给调用者。

这意味着协程可以在不同的时间点多次产生值,而不是像普通函数那样一次性返回一个结果
当调用者再次请求协程继续执行时,协程会从 co_yield 之后的位置恢复执行。

避免了传统异步编程中的回调地狱问题。

Generator generate_numbers() {
    for (int i = 0; i < 5; ++i) {
        co_yield i; // 生成值并挂起
    }
}

每执行一次 co_yield i,协程框架就会调用 Generator 类(当前协程函数的返回值类型)中 promise_type 结构体yield_value 方法,并且把 i 的值传递给该方法。

基本用法

简单生成器示例,生成从 0 到 4 的整数

#include <coroutine>
#include <iostream>
#include <cstdlib>

// 生成器类定义
class Generator {
public:
    struct promise_type { // 【定义了协程的行为】 (包括如何初始化、挂起、恢复、处理异常以及产生值等)
        int 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 unhandled_exception() { std::exit(1); } // 异常处理
        
        std::suspend_always yield_value(int value) { // ※ 用于处理 co_yield 语句
            current_value = value;
            return {};
        }
        
        void return_void() {} // 处理 co_return 语句,这里的协程不返回值。
    };

    using handle_type = std::coroutine_handle<promise_type>;//协程的句柄,对协程进行控制与管理

    // 迭代器类
    struct Iterator {
        handle_type coro_handle;

        bool operator!=(const Iterator&) const { return !coro_handle.done(); }
        void operator++() { coro_handle.resume(); }
        int operator*() const { return coro_handle.promise().current_value; } // 获取值
    };

    // 构造函数和析构函数 : 接收/销毁 句柄
    explicit Generator(handle_type h) : coro_handle(h) {}
    ~Generator() { if (coro_handle) coro_handle.destroy(); }

    // 支持范围循环
    Iterator begin() {
        if (coro_handle) coro_handle.resume();
        return Iterator{coro_handle};
    }
    
    Iterator end() { return Iterator{nullptr}; }

private:
    handle_type coro_handle;
};

// 协程函数
Generator generate_numbers() {
    for (int i = 0; i < 5; ++i) {
        co_yield i; // 生成值并挂起
    }
}

// 使用示例
int main() {
    for (auto num : generate_numbers()) {
        std::cout << "Generated: " << num << std::endl;
    }
    return 0;
}
    

优势

  • 异步非阻塞:
    协程通过 co_await 挂起执行但不阻塞线程,可以在等待 I/O 时释放 CPU 资源
    单线程即可处理大量并发 I/O 请求(如网络连接、文件读写)

像网络IO,磁盘IO等,CPU只是调度,真正执行的是硬件:网卡和磁盘。
所以程序中高IO的情况下,不想阻塞IO,就可以使用协程。

生命周期结束,协程可以正常进行。
IO异常后,协程也会抛出异常。

  • 低开销
    协程切换成本远低于线程切换(通常是纳秒级 vs 微秒级)。

异步 TCP

#include <boost/asio.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <coroutine>
#include <iostream>

namespace asio = boost::asio;
using asio::ip::tcp;

// 协程异步连接和读写
asio::awaitable<void> async_client() { // 该协程不返回任何值
    try {
        auto executor = co_await asio::this_coro::executor; //获取当前协程的执行器,用于创建 tcp::socket 对象
        
		for (int i = 0; i < 5; ++i) {
			tcp::socket socket(executor);
	
	        // 异步连接(非阻塞)
	        co_await socket.async_connect( // 【异步连接】
	            tcp::endpoint(asio::ip::make_address("127.0.0.1"), 8080),
	            asio::use_awaitable // 协程挂起等, 控制权返回给事件循环,事件循环可以继续处理其他异步操作。
	        );
	
	        // 异步发送数据
	        std::string request = "Hello from coroutine!";
	        co_await asio::async_write(socket, asio::buffer(request), asio::use_awaitable); // 【异步发送】
	
	        // 异步接收响应
	        char response[1024];
	        size_t len = co_await socket.async_read_some(asio::buffer(response), asio::use_awaitable); // 【异步接收】
	        std::cout << "Received: " << std::string(response, len) << std::endl;
	    }
	    
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

int main() {
    asio::io_context io_context; // Boost.Asio 的核心对象,负责管理所有的异步操作和事件循环。

    for (int i = 0; i < 5; ++i) {
    	// 启动协程任务
        asio::co_spawn(io_context, async_client(), asio::detached); 
        //           (执行器,     协程函数,      协程任务独立运行 不等待)
    }

    // 运行事件循环
    io_context.run(); 
    return 0;
}

co_await

关键字。让异步代码可以以同步的形式编写。

C++20 之前,没有 co_await 这个关键字,异步编程通常借助回调函数或者 std::future 与 std::promise 来实现,难以维护。

后面通常跟着一个可等待对象(Awaitable),这个对象需要实现特定的接口:

  • await_ready 判断可等待对象是否已经准备好
  • await_suspend 当 await_ready 返回 false 时,await_suspend 方法会被调用。这个方法负责挂起协程,并安排在可等待对象完成时恢复协程的执行。
  • await_resume 当可等待对象完成时,await_resume 方法会被调用。该方法的返回值会作为 co_await 表达式的结果返回给协程。
#include <iostream>
#include <coroutine>
#include <future>
#include <thread>

// 定义一个可等待对象
struct Awaitable {
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> handle) const noexcept {
        std::thread([handle]() {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            handle.resume();
        }).detach();
    }
    void await_resume() const noexcept {}
};

// 协程函数
std::coroutine_handle<> coroutine_handle; // 协程句柄声明,这里没有使用
struct Task { // 协程函数的返回类型, 同“基本用法”的Generator类
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

Task coroutine_function() {
    std::cout << "Coroutine started." << std::endl;
    co_await Awaitable{};
    std::cout << "Coroutine resumed after waiting." << std::endl;
}

int main() {
    coroutine_function();
    std::cout << "Main function continues." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}

相关文章:

  • 深圳有哪些做网站公司好云搜索系统
  • 前几年做那个网站致富百度手机助手官网
  • 在线做网站午夜伦理人民网 疫情
  • 金华网站建设多少钱全网营销策划公司
  • 网站如何做seo的他达拉非片正确服用方法
  • 制作网页csdnseo外包方法
  • 综合实验一
  • Arduino示例代码讲解:Virtual Color Mixer 虚拟混色器
  • CS提取的基本使用和模块加载
  • 树莓派超全系列文档--(14)无需交互使用raspi-config工具其一
  • 记录vite-plugin-dts打包时无法生成 .d.ts文件问题
  • Numpy常见bug
  • 定时器的定义
  • linux用户组和用户
  • MyBatis复杂查询——一对一、一对多
  • AF3 FeaturePipeline类解读
  • 经典动态规划问题:爬楼梯的多种解法详解
  • 基于大模型的知识图谱搜索的五大核心优势
  • 每日c/c++题 备战蓝桥杯(二分答案模版)
  • 函数指针在C++遍历函数中的写法和应用(直接在函数中定义函数指针)。
  • Python调用手机摄像头检测火焰烟雾的三种方法
  • python定时调度升级
  • 使用 Ansys Discovery 可视化液体池中的水流
  • ES拼音分词自动补全实现
  • LLMs之PE:《Tracing the thoughts of a large language model》翻译与解读
  • 单例模式解析