协程 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;
}

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

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

相关文章

uniapp中的流式输出

一、完整代码展示 目前大多数的ai对话都是流式输出&#xff0c;也就是对话是一个字或者多个字逐一进行显示的下面是一个完整的流式显示程序&#xff0c;包含的用户的消息发出和ai的消息回复 <template><view class"chat-container"><view class&quo…

洛谷题单1-P5703 【深基2.例5】苹果采购-python-流程图重构

题目描述 现在需要采购一些苹果&#xff0c;每名同学都可以分到固定数量的苹果&#xff0c;并且已经知道了同学的数量&#xff0c;请问需要采购多少个苹果&#xff1f; 输入格式 输入两个不超过 1 0 9 10^9 109 正整数&#xff0c;分别表示每人分到的数量和同学的人数。 输…

JS 手撕题高频考点

前端面试中&#xff0c;JS 手撕题是高频考点&#xff0c;主要考察 编程能力、算法思维、JS 核心知识。以下是最常见的手撕题分类 代码示例&#xff1a; 目录 &#x1f4cc; 1. 手写函数柯里化&#x1f4cc; 2. 手写 debounce&#xff08;防抖&#xff09;&#x1f4cc; 3. 手写…

【STM32】知识点介绍一:硬件知识

文章目录 一、电源引脚简介二、电平信号三、电路分析 一、电源引脚简介 VCC、GND、VDD和VSS是电子电路中常见的术语&#xff0c;代表着不同的电源引脚或电压。 VCC&#xff08;Voltage at the Common Collector&#xff09;&#xff1a;VCC是指集电极&#xff08;Collector&am…

3. 列表元素替换

【问题描述】给定一个列表&#xff0c;将列表中所有的偶数替换为0 【输入形式】输入一行&#xff0c;包含若干个整数&#xff0c;用空格分隔 【输出形式】输出替换后的列表&#xff0c;每个元素用空格分隔 【样例输入】1 2 3 4 5 6 7 8 9 10 【样例输出】1 0 3 0 5 0 7 0 9…

问题的根源还是解题的方案

周末的早上照例是要早醒 debug 代码的&#xff0c;仿佛又回到了 2014 年… 古人几天甚至几个月不洗澡&#xff0c;不会臭吗&#xff1f;有没有可能古人没有化纤类衣服&#xff0c;且古人的纯天然生活环境其身体菌群和现代人不同&#xff0c;古人就像健康的野生动物一样即使不洗…

虚拟机安装linux系统无法上网的解决方法

在虚拟环境中运行Linux系统时&#xff0c;有时会遇到网络连接问题&#xff0c;特别是在使用虚拟机软件如VMware或VirtualBox时。本文将详细介绍一种针对“虚拟机安装Linux系统无法上网”问题的解决方案&#xff0c;以CentOS 6.5为例&#xff0c;适用于其他基于NAT模式的虚拟机环…

子网划分浅度解析

文章目录 ip地址的组成不同类型ip地址的范围子网掩码默认子网掩码子网掩码如何作用的&#xff1f;默认子网掩码怎么作用&#xff1f; ip地址的组成 ip地址一般写作4位点分十进制&#xff08;x.x.x.x&#xff09;&#xff0c;他们由32位二进制组成&#xff0c;每个x由8位二进制…

什么是 SEO(搜索引擎优化)?

您有网站吗&#xff0c;或者您正在考虑创建一个网站&#xff1f;您想吸引更多人加入您的业务吗&#xff1f;如果答案是肯定的&#xff0c;那么毫无疑问&#xff1a;SEO 应该是您营销工作的一部分。这是建立品牌和吸引用户访问您的网站的好方法。但它实际上意味着什么呢&#xf…

鸿蒙HarmonyOS NEXT设备升级应用数据迁移流程

数据迁移是什么 什么是数据迁移&#xff0c;对用户来讲就是本地数据的迁移&#xff0c;终端设备从HarmonyOS 3.1 Release API 9及之前版本&#xff08;单框架&#xff09;迁移到HarmonyOS NEXT&#xff08;双框架&#xff09;后保证本地数据不丢失。例如&#xff0c;我在某APP…

【现代深度学习技术】现代卷积神经网络04:含并行连接的网络(GoogLeNet)

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…

【ESP32】ESP32与MQTT通信:实现传感器数据监测与设备控制

ESP32与MQTT通信 1 项目概览2 硬件组成3 MQTT协议解析MQTT协议简介MQTT核心概念本项目中的MQTT应用 4 MQTT Broker选择EMQX Broker其他常用MQTT Broker 5 代码解析初始化与配置MQTT消息处理发布传感器数据 6 MQTT话题TOPIC设计7 EMQX的优势在IoT项目中的体现8 MQTT通信流程9 应…

[特殊字符]《Curve DAO 系统学习目录》

本教程旨在系统学习 Curve DAO 项目的整体架构、核心机制、合约设计、治理逻辑与代币经济等内容&#xff0c;帮助开发者全面理解其设计理念及运作方式。 目录总览&#xff1a; 1. Curve 项目概览 • 1.1 Curve 是什么&#xff1f;主要解决什么问题&#xff1f; • 1.2 与其他…

每天一篇目标检测文献(六)——Part One

今天看的是《Object Detection with Deep Learning: A Review》 目录 一、摘要 1.1 原文 1.2 翻译 二、介绍 2.1 信息区域选择 2.2 特征提取 2.3 分类 三、深度学习的简要回顾 3.1 历史、诞生、衰落和繁荣 3.2 CNN架构和优势 一、摘要 1.1 原文 Due to object dete…

Arthas线上问题诊断器

Arthas是Alibaba开源的java诊断工具 解决问题 这个类从哪个jar 包加载的&#xff1f;为什么会报各种相关的Exception&#xff1f; 遇到问题无法在线上debug&#xff0c;不能直通过加载日志再重新发布 有什么办法可以监控到JVM的实时运行状态&#xff1f; …

[Lc5_dfs+floodfill] 简介 | 图像渲染 | 岛屿数量

目录 0.floodfill算法简介 1.图像渲染 题解 2.岛屿数量 题解 之前我们在 bfs 中有介绍过[Lc15_bfsfloodfill] 图像渲染 | 岛屿数量 | 岛屿的最大面积 | 被围绕的区域&#xff0c;现在我们来看看 dfs 又是如何解决的呢 0.floodfill算法简介 floodfill算法又叫洪水灌溉或者…

JVM类加载器详解

文章目录 1.类与类加载器2.类加载器加载规则3.JVM 中内置的三个重要类加载器为什么 获取到 ClassLoader 为null就是 BootstrapClassLoader 加载的呢&#xff1f; 4.自定义类加载器什么时候需要自定义类加载器代码示例 5.双亲委派模式类与类加载器双亲委派模型双亲委派模型的执行…

Chapters 15 16:What Is Architecture?Independence_《clean architecture》notes

What Is Architecture?&Independence **Chapter 15: What Is Architecture?****Key Concepts**:**Code Example: Layered Architecture**: **Chapter 16: Independence****Key Concepts**:**Code Example: Dependency Inversion & Interfaces**: **Combined Example:…

【SPP】RFCOMM 层在SPP中互操作性要求深度解析

蓝牙串口协议&#xff08;SPP&#xff09;通过 RFCOMM 协议实现 RS232 串口仿真&#xff0c;其互操作性是设备互联的关键。本文基于蓝牙核心规范&#xff0c;深度解析 RFCOMM 层的能力矩阵、信号处理、流控机制及实战开发&#xff0c;结合状态机、流程图和代码示例&#xff0c;…

阻塞式IO与非阻塞IO的区别

阻塞式IO与非阻塞IO的区别 1. 阻塞式IO (Blocking I/O) 定义 当程序发起一个I/O操作&#xff08;如读取文件、网络数据&#xff09;时&#xff0c;进程会被挂起&#xff08;阻塞&#xff09;&#xff0c;直到操作完成或超时才会继续执行后续代码。在此期间&#xff0c;程序无法…