C++协程(Coroutine)简介

news2025/1/5 15:09:16

文章目录

    • 引言
      • 1. 简化异步编程
        • 问题:
        • 协程的解决方式:
      • 2. 高效的并发控制
        • 问题:
        • 协程的解决方式:
      • 3. 简化生成器(Generators)和数据流处理
        • 问题:
        • 协程的解决方式:
      • 适用场景
    • C++ 协程
    • 协程的运行机制
    • C++20 协程的用法示例
      • 示例 1. coroutine 执行流程
        • 定义一个协程
        • 调用协程
        • 完整代码
      • 示例 2. generator
    • 协程的优缺点
      • 优点
      • 缺点
    • 实践建议
    • 结论

引言

C++20 的发布标志着协程(coroutines)首次被纳入标准语言. 这一功能通过对异步编程的支持和生成器模式的实现, 极大地改变了程序员处理复杂控制流的方式. 本文将详细介绍 C++20 协程的核心概念, 实现原理, 用法示例以及它在实际应用中的潜力.

C++ 协程(coroutine) 是一种高级控制流工具, 旨在简化异步编程和并发操作. 它通过将函数的执行状态保存并恢复, 允许程序从挂起的位置继续执行, 从而解决了一些传统方法难以优雅处理的问题. 以下是协程解决的主要问题:


1. 简化异步编程

问题:

传统的异步编程需要使用回调(callbacks), 状态机或复杂的线程管理. 这样代码会变得难以理解和维护, 特别是在处理多层嵌套的回调时, 容易导致 回调地狱(callback hell).

协程的解决方式:

协程允许以同步风格编写异步代码. 例如:

  • 使用 co_await 直接等待异步操作完成, 而不是通过回调.
  • 消除嵌套, 代码逻辑更加线性, 易读.

示例:

std::future<void> downloadFile() {
    auto data = co_await asyncDownload();
    process(data);
    co_return;
}

2. 高效的并发控制

问题:

传统的线程模型可能因线程切换的开销过大而导致性能问题, 特别是在 I/O 密集型任务中. 使用线程池可以缓解这个问题, 但增加了复杂性.

协程的解决方式:

协程是"协作式多任务"(cooperative multitasking), 没有线程切换的上下文开销, 提供了轻量级的并发支持. 多个协程可以共享同一个线程, 从而显著提升资源利用率.


3. 简化生成器(Generators)和数据流处理

问题:

生成器和数据流处理中需要保存中间状态, 传统实现需要额外管理状态机或全局变量.

协程的解决方式:

协程通过 co_yield 自动管理状态, 可以轻松实现生成器模式, 用于惰性生成数据流.

示例:

// generator 是一个自定义的模板类
generator<int> numbers() {
    for (int i = 0; i < 10; ++i) {
        co_yield i;
    }
}

调用 numbers() 会逐步生成数据, 而无需手动管理迭代器状态.


适用场景

  1. 网络编程: 高性能服务器需要同时处理大量异步请求, 协程使得代码逻辑清晰且高效.
  2. 游戏开发: 游戏中的物理引擎, AI 行为等需要频繁的状态切换, 协程简化了这些逻辑.
  3. 数据流处理: 协程的生成器模式非常适合逐步处理大数据集, 如大规模日志处理和流式计算.
  4. 并发任务管理: 如多任务调度器或事件循环.

C++ 协程

C++20 为协程提供了语言级支持, 通过以下三个关键字实现核心功能:

  • co_await: 用于挂起协程的执行, 等待某个任务完成.
  • co_yield: 产生一个值并挂起协程, 允许调用者获取生成的值.
  • co_return: 终止协程并返回值.

协程的运行机制

C++20 的协程支持依赖于编译器生成的协程框架, 这一框架包含:

  1. 协程句柄 (Coroutine Handle)

    • std::coroutine_handle 是核心组件, 用于管理协程的生命周期.
    • 它可以启动, 暂停, 恢复和销毁协程.
  2. 协程状态 (Coroutine State)

    • 协程状态存储在协程帧 (coroutine frame) 中, 包含局部变量, 程序计数器等信息.
    • 每个协程帧由编译器分配, 允许协程挂起时保持状态.
  3. 协程特性 (Coroutine Traits)

    • 标准库的 std::coroutine_traits 用于决定协程的返回类型.
    • 开发者可以通过特化 std::coroutine_traits 来定制协程的行为.

C++20 协程的用法示例

示例 1. coroutine 执行流程

定义一个协程
Task GetTask() {
  std::println("One!");
  co_await std::suspend_always{};  // 协程在此处第一次挂起

  std::println("Two!");
  co_await std::suspend_always{};  // 协程在此处第二次挂起

  std::println("Three!");
}

GetTask定义了一个协程, 直观的看这个函数体将会输出三个语句, 只不过期间会有暂停, co_await 将会挂起操作直到resume()方法被调用.

这个函数没有return语句却有返回值:Task. 这是协程的特性, 我们需要一些接口和胶水代码来与协程交互.

调用协程
int main() {
  Task task = GetTask();
  std::println("coroutine GetTask() started");

  while (task.resume()) {
    using namespace std::literals;  // 为了使用 ms 后缀
    std::this_thread::sleep_for(500ms);
  }
  return 0;
}
完整代码
#include <chrono>
#include <coroutine>
#include <print>
#include <thread>

// Promise 模板类, 用于管理协程的状态和生命周期
template <typename T>
struct Promise {
  // 获取返回对象的方法
  auto get_return_object() {
    std::println("Promise::get_return_object()");
    // 从当前 promise 创建一个协程句柄
    return std::coroutine_handle<Promise<T>>::from_promise(*this);
  }

  // 初始挂起点, 在协程开始时调用
  auto initial_suspend() {
    std::println("Promise::initial_suspend()");
    // 返回一个总是挂起的挂起点
    return std::suspend_always{};
  }

  // 最终挂起点, 在协程结束时调用
  auto final_suspend() noexcept {
    std::println("Promise::final_suspend()");
    // 返回一个总是挂起的挂起点
    return std::suspend_always{};
  }

  // 处理未捕获的异常
  void unhandled_exception() { std::terminate(); }

  // 返回 void 类型的方法
  void return_void() { std::println("Promise::return_void()"); }
};

// Task 类, 表示一个协程任务
class Task {
 public:
  // 定义 promise_type 为 Promise<Task>
  using promise_type = Promise<Task>;

  // 构造函数, 接受一个协程句柄
  Task(auto h) : handle_{h} { std::println("Task::construct"); }

  // 析构函数, 销毁协程句柄
  ~Task() {
    std::println("Task::destruct");
    if (handle_) {
      handle_.destroy();
    }
  }

  // 恢复协程的方法
  bool resume() const {
    std::println("Task::resume()");
    if (!handle_) {
      return false;
    }
    handle_.resume();
    return !handle_.done();
  }

 private:
  // 协程句柄, 管理协程的执行
  std::coroutine_handle<promise_type> handle_;
};

// GetTask 函数定义了一个协程任务
// 协程在调用 GetTask() 时创建, 并在 co_await 处挂起
Task GetTask() {
  std::println("One!");
  co_await std::suspend_always{};  // 协程在此处第一次挂起

  std::println("Two!");
  co_await std::suspend_always{};  // 协程在此处第二次挂起

  std::println("Three!");
}

int main() {
  Task task = GetTask();
  std::println("coroutine GetTask() started");

  while (task.resume()) {
    using namespace std::literals;  // 为了使用 ms 后缀
    std::this_thread::sleep_for(500ms);
  }
  return 0;
}

在 Compiler Explorer 中查看

运行输出信息:

Promise::get_return_object()
Promise::initial_suspend()
Task::construct
coroutine GetTask() started
Task::resume()
One!
Task::resume()
Two!
Task::resume()
Three!
Promise::return_void()
Promise::final_suspend()
Task::destruct

示例 2. generator

#include <coroutine>
#include <exception>
#include <iostream>
#include <print>
#include <thread>
#include <vector>

// Generator 类, 表示一个生成器协程
class Generator {
 public:
  // promise_type 结构体, 管理协程的状态和生命周期
  struct promise_type {
    int value_ = 0;  // 当前值

    // yield_value 方法, 用于挂起协程并保存当前值
    auto yield_value(int value) {
      value_ = value;
      return std::suspend_always{};  // 总是挂起协程
    }

    // get_return_object 方法, 返回协程句柄
    auto get_return_object() {
      return std::coroutine_handle<promise_type>::from_promise(*this);
    }

    // initial_suspend 方法, 初始挂起点
    auto initial_suspend() { return std::suspend_always{}; }

    // final_suspend 方法, 最终挂起点
    auto final_suspend() noexcept { return std::suspend_always{}; }

    // unhandled_exception 方法, 处理未捕获的异常
    void unhandled_exception() { std::terminate(); }

    // return_void 方法, 协程返回 void 类型
    void return_void() {}
  };

 public:
  // 构造函数, 接受一个协程句柄
  Generator(auto h) : handle_{h} {}

  // 析构函数, 销毁协程句柄
  ~Generator() {
    if (handle_) {
      handle_.destroy();
    }
  }

  // 恢复协程的方法
  bool resume() const {
    if (!handle_) {
      return false;
    }

    handle_.resume();
    return !handle_.done();
  }

  // 获取当前值的方法
  int getValue() const { return handle_.promise().value_; }

 private:
  // 协程句柄, 管理协程的执行
  std::coroutine_handle<promise_type> handle_;
};

template <typename T>
Generator Visit(const T& coll) {
  for (int elem : coll) {
    std::println("\tyield {}", elem);
    co_yield elem;
    std::println("\tresume");
  }
}

int main() {
  using namespace std::literals;

  std::vector<int> coll{0, 8, 15, 33, 42, 77};
  Generator gen = Visit(coll);

  std::println("start loop:");
  while (gen.resume()) {
    std::println("main(): value: {}", gen.getValue());
    std::this_thread::sleep_for(1s);
  }
}

在 Compiler Explorer 中查看

输出:

start loop:
        yield 0
main(): value: 0
        resume
        yield 8
main(): value: 8
        resume
        yield 15
main(): value: 15
        resume
        yield 33
main(): value: 33
        resume
        yield 42
main(): value: 42
        resume
        yield 77
main(): value: 77
        resume

协程的优缺点

优点

  1. 可读性: 使异步代码的逻辑更加线性, 避免嵌套的回调地狱.
  2. 性能: 通过避免线程切换和不必要的上下文切换, 提升效率.
  3. 灵活性: 适用于多种异步任务, 例如 I/O, 网络请求等.

缺点

  1. 复杂性: 需要深入理解协程框架和生命周期.
  2. 资源管理: 协程暂停时占用内存, 可能导致内存泄漏.
  3. 兼容性: 现有的库和工具对协程支持有限, 需要手动整合.

实践建议

  • 避免内存泄漏: 在协程销毁前正确释放资源.

  • 合理场景使用: 协程更适合 I/O 密集型任务, 不适合所有场景.

  • 深入学习机制: 理解协程帧和句柄的运作, 便于调试和优化.

结论

C++20 协程的引入让异步编程变得更加简单和高效. 无论是高性能服务器还是游戏开发, 协程都提供了一种直观且强大的解决方案. 通过熟练掌握协程的核心机制, 开发者可以显著提升代码的可维护性和运行效率.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2270249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《类和对象:基础原理全解析(下篇)》

目录 一、类的构造函数的初始化列表1. 初始化列表的使用2. 初始化列表的初始化顺序3. 使用初始化列表的注意事项 二、类的自动类型转换1. 类的自动类型转换的使用2. 关闭类的自动类型转换 三、静态类成员1. 静态成员的特性2. 使用静态成员计算类创建了多少个对象3. 使用静态类成…

分析服务器 systemctl 启动gozero项目报错的解决方案

### 分析 systemctl start beisen.service 报错 在 Linux 系统中&#xff0c;systemctl 是管理系统和服务的主要工具。当我们尝试重启某个服务时&#xff0c;如果服务启动失败&#xff0c;systemctl 会输出错误信息&#xff0c;帮助我们诊断和解决问题。 本文将通过一个实际的…

Crosslink-NX应用连载(12):如何复用特殊功能管脚

作者&#xff1a;Hello,Panda 大家早上好。 昨天有朋友私信我&#xff0c;如何复用Crosslink-NX的特殊功能引脚如PROGRAMN、DONE、INITN诸如这些。熊猫君在这里简单介绍下&#xff1a; 以LIFCL-33U-8CTG104C为例&#xff0c;我们建立一个简单的指示灯LED周期闪烁的工程&…

go项目使用gentool生成model的gen.go问题

Gen Tool 是一个没有依赖关系的二进制文件&#xff0c;可以用来从数据库生成结构。 使用方法&#xff1a; go install gorm.io/gen/tools/gentoollatest在项目根目录,执行连接的数据库中指定某几张表结构生成数据库model层 gentool -dsn "root:123456tcp(localhost:330…

家政上门小程序如何创建?家政服务怎么能少了小程序帮手

在如今这个“忙到没时间打扫”的时代&#xff0c;家政服务变得越来越受欢迎。为了提高效率、减少沟通成本&#xff0c;很多家政公司都已经开始借助小程序的力量。那么&#xff0c;家政上门小程序到底该如何创建呢?小程序又是如何帮助家政服务更好地满足客户需求的呢?本文将为…

破解密码

rhel8/centos8 重置 root 密码 方法 1 &#xff1a; rd.break 第 1 步 重启系统&#xff0c;在下图所示界面按 e 键 第2步 找到linux这行&#xff0c;末尾空格后 输入 rd.break 第3步 查看&#xff0c;可选步骤 这里 sysroot 是以只读的形式挂载的&#xff0c;所以要以可读可…

本地小主机安装HomeAssistant开源智能家居平台打造个人AI管家

文章目录 前言1. 添加镜像源2. 部署HomeAssistant3. HA系统初始化配置4. HA系统添加智能设备4.1 添加已发现的设备4.2 添加HACS插件安装设备 5. 安装cpolar内网穿透5.1 配置HA公网地址 6. 配置固定公网地址 前言 大家好&#xff01;今天我要向大家展示如何将一台迷你的香橙派Z…

自学记录鸿蒙API 13:实现多目标识别Object Detection

起步&#xff1a;什么叫多目标识别&#xff1f; 无论是生活中的动物识别、智能相册中的场景分类&#xff0c;还是工业领域的检测任务&#xff0c;都能看到多目标识别的身影。这次&#xff0c;我决定通过学习HarmonyOS最新的Object Detection API&#xff08;API 13&#xff09…

javaEE-多线程进阶-JUC的常见类

juc:指的是java.util.concurrent包&#xff0c;该包中加载了一些有关的多线程有关的类。 目录 一、Callable接口 FutureTask类 参考代码&#xff1a; 二、ReentrantLock 可重入锁 ReentrantLock和synchronized的区别&#xff1a; 1.ReentantLock还有一个方法&#xff1a…

fpga系列 HDL:ModelSim显示模拟波形+十进制格式数值(临时方法和设置持久化的默认值)

模拟波形 FPGA中使用数字滤波器时&#xff0c;可通过观察模拟波形更好地查看滤波效果。可以通过ModelSim中的波形格式设置来实现更直观的波形显示。右键波形->Format-> Analog 效果 数值格式显示 不同的数值格式显示&#xff1a;右键波形->Radix-> Decimal 效果…

Linux 中 sysctl 和 systemctl 有什么区别?

sysctl 和 systemctl 是两个不同的命令行工具&#xff0c;它们在 Linux 系统中分别用于不同的目的。理解这两个命令的区别对于系统管理和配置非常重要。 1. sysctl 功能 用途&#xff1a;sysctl 用于动态地修改内核参数&#xff0c;这些参数控制着操作系统的某些行为。配置文…

【ArcGISPro/GeoScenePro】检查并处理高程数据

数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 数字高程模型 (DEM) 是一种栅格,可显示地面或地形的高程。 数字表面模型 (DSM) 是另一种高程栅格,可显示表面的高度,例如建筑物或树冠的顶部。 您需要准备 DEM 和 DSM 以供分析…

Redis数据库主要数据结构类型

Redis数据库提供了丰富多样的数据结构类型&#xff0c;以满足不同场景下的数据存储需求。以下是Redis中的主要数据结构类型&#xff1a; 一、五种基础数据结构 字符串&#xff08;String&#xff09; 简介&#xff1a;字符串是Redis最基本的数据类型&#xff0c;可以存储字符串…

基于Springboot + vue实现的校园周边美食探索及分享平台

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(1):Oracle Dataguard 概述

Oracle Dataguard&#xff08;主库为 Oracle 11g 单节点&#xff09;配置详解&#xff08;1&#xff09;&#xff1a;Oracle Dataguard 概述 目录 Oracle Dataguard&#xff08;主库为 Oracle 11g 单节点&#xff09;配置详解&#xff08;1&#xff09;&#xff1a;Oracle Data…

mapbox基础,测面功能实现

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️Turf 框架二、🍀测面功能实现1. ☘️实现思路2. ☘️代码样例一、🍀…

基于下垂控制的构网变换器功率控制【微电网变流器】【Simulink】

目录 主要内容 理论研究 整体模型 PQ计算模块 功率控制模块 PWM反馈模块 结果一览 下载链接 主要内容 该仿真针对微电网中分布式电源接入后产生的谐波影响&#xff0c;除了污染网络外&#xff0c;还会恶化微电网变流器输出电流&#xff0c;为了消除谐波影响&a…

2025差旅平台推荐:一体化降本30%

医药行业因其高度专业化的特点&#xff0c;同时在运营过程中又极为依赖供应链和销售网络&#xff0c;因此差旅管理往往成为成本控制的重要环节。本期&#xff0c;我们以差旅平台分贝通签约伙伴——某知名药企为例&#xff0c;探讨企业如何通过差旅一体化管理&#xff0c;在全流…

【漫话机器学习系列】027.混淆矩阵(confusion matrix)

混淆矩阵&#xff08;Confusion Matrix&#xff09; 混淆矩阵是机器学习中评估分类模型性能的一种工具&#xff0c;特别是在多类别分类问题中。它通过对比模型预测结果和真实标签&#xff0c;帮助我们理解模型的分类效果。 1. 混淆矩阵的结构 混淆矩阵通常是一个二维表格&am…

【AIGC】 ChatGPT实战教程:如何高效撰写学术论文引言

&#x1f4a5; 欢迎来到我的博客&#xff01;很高兴能在这里与您相遇&#xff01; 首页&#xff1a;GPT-千鑫 – 热爱AI、热爱Python的天选打工人&#xff0c;活到老学到老&#xff01;&#xff01;&#xff01;导航 - 人工智能系列&#xff1a;包含 OpenAI API Key教程, 50个…