news 2026/4/26 1:18:16

【C++】从实战日志读懂C++20协程:异步操作的优雅实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++】从实战日志读懂C++20协程:异步操作的优雅实现

文章目录

  • 从实战日志读懂C++20协程:异步操作的优雅实现
    • 零、代码和日志
      • 代码
      • 日志
    • 一、代码场景与核心目标
    • 二、协程核心组件拆解
      • 1. 可等待对象(AsyncOperation)
      • 2. Promise类型(Task::promise_type)
      • 3. 协程句柄(coroutine_handle)
    • 三、从日志看协程完整执行流程
      • 阶段1:协程创建与初始化
      • 阶段2:第一次co_await(协程暂停)
      • 阶段3:异步操作执行与协程恢复
      • 阶段4:第二次co_await(重复暂停-恢复)
      • 阶段5:第三次co_await(无暂停)
      • 阶段6:协程收尾与资源释放
    • 四、关键知识点总结
    • 五、协程的优势与适用场景

从实战日志读懂C++20协程:异步操作的优雅实现

协程(Coroutine)是C++20引入的核心特性,它打破了传统函数“一次性执行完毕”的执行模型,允许函数在执行过程中暂停(suspend)和恢复(resume),是实现异步编程、生成器等场景的利器。本文将结合一段带详细日志的协程实战代码,从执行流程、核心组件、关键机制三个维度,拆解C++协程的工作原理。

零、代码和日志

代码

#include<iostream>#include<coroutine>#include<thread>#include<chrono>// 增强的可等待对象,添加更多日志structAsyncOperation{intvalue;boolready=false;constchar*name;AsyncOperation(intv,boolr,constchar*n=""):value(v),ready(r),name(n){std::cout<<"["<<name<<"] AsyncOperation 构造: value="<<value<<", ready="<<ready<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;}boolawait_ready()constnoexcept{std::cout<<"["<<name<<"] await_ready() 调用, 返回: "<<ready<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;returnready;}voidawait_suspend(std::coroutine_handle<>handle)noexcept{std::cout<<"["<<name<<"] await_suspend() 调用, 协程句柄: "<<handle.address()<<" (线程 : "<<std::this_thread::get_id()<<") "<<std::endl;// 启动异步操作std::thread([this,handle,name=this->name](){std::cout<<"["<<name<<"] 异步线程开始 (线程: "<<std::this_thread::get_id()<<")"<<std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));this->ready=true;std::cout<<"["<<name<<"] 异步操作完成, 准备恢复协程"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;// 恢复协程执行handle.resume();std::cout<<"["<<name<<"] 异步线程结束"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;}).detach();std::cout<<"["<<name<<"] await_suspend() 返回, 协程即将挂起"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;}intawait_resume()noexcept{std::cout<<"["<<name<<"] await_resume() 调用, 返回结果: "<<value<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;returnvalue;}};structTask{structpromise_type{intcurrent_value;Taskget_return_object(){std::cout<<"promise_type::get_return_object() 调用"<<std::endl;returnTask{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_neverinitial_suspend()noexcept{std::cout<<"promise_type::initial_suspend() 调用 - 立即开始执行"<<std::endl;return{};}std::suspend_alwaysfinal_suspend()noexcept{std::cout<<"promise_type::final_suspend() 调用 - 协程结束"<<std::endl;return{};}voidunhandled_exception(){std::cout<<"promise_type::unhandled_exception() 调用"<<std::endl;}voidreturn_void(){std::cout<<"promise_type::return_void() 调用 - 协程正常返回"<<std::endl;}};std::coroutine_handle<promise_type>handle;Task(std::coroutine_handle<promise_type>h):handle(h){std::cout<<"Task 对象构造, 句柄: "<<handle.address()<<std::endl;}~Task(){if(handle){std::cout<<"Task 析构, 销毁协程句柄"<<std::endl;handle.destroy();}}};Taskdetailed_async_example(){std::cout<<"=== 协程函数开始执行 ==="<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;std::cout<<"\n--- 第一次 co_await ---"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;intresult1=co_awaitAsyncOperation{100,false,"操作1"};std::cout<<"第一次 co_await 后继续执行, 结果: "<<result1<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;std::cout<<"\n--- 第二次 co_await ---"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;intresult2=co_awaitAsyncOperation{200,false,"操作2"};std::cout<<"第二次 co_await 后继续执行, 结果: "<<result2<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;std::cout<<"\n--- 第三次 co_await (就绪状态) ---"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;intresult3=co_awaitAsyncOperation{300,true,"操作3"};std::cout<<"第三次 co_await 后继续执行, 结果: "<<result3<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;std::cout<<"\n=== 协程函数执行完成 ==="<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;}intmain(){std::cout<<"主函数开始 (线程: "<<std::this_thread::get_id()<<")"<<std::endl;{std::cout<<"\n********** 创建协程任务 **********"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;autotask=detailed_async_example();std::cout<<"协程任务已创建,控制权返回主函数"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;// 给异步操作时间完成std::cout<<"\n主线程等待 3 秒..."<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;std::this_thread::sleep_for(std::chrono::seconds(3));std::cout<<"主线程等待结束"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;}// task 析构std::cout<<"\n主函数结束"<<" (线程: "<<std::this_thread::get_id()<<")"<<std::endl;return0;}

日志

主函数开始(线程:132140388288320)********** 创建协程任务 **********(线程:132140388288320)promise_type::get_return_object()调用 Task 对象构造, 句柄: 0x5e3b8cf602c0 promise_type::initial_suspend()调用 - 立即开始执行===协程函数开始执行===(线程:132140388288320)--- 第一次 co_await ---(线程:132140388288320)[操作1]AsyncOperation 构造:value=100,ready=0(线程:132140388288320)[操作1]await_ready()调用, 返回:0(线程:132140388288320)[操作1]await_suspend()调用, 协程句柄: 0x5e3b8cf602c0(线程:132140388288320)[操作1]await_suspend()返回, 协程即将挂起(线程:132140388288320)协程任务已创建,控制权返回主函数(线程:132140388288320)主线程等待3...(线程:132140388288320)[操作1]异步线程开始(线程:132140387796544)[操作1]异步操作完成, 准备恢复协程(线程:132140387796544)[操作1]await_resume()调用, 返回结果:100(线程:132140387796544)第一次 co_await 后继续执行, 结果:100(线程:132140387796544)--- 第二次 co_await ---(线程:132140387796544)[操作2]AsyncOperation 构造:value=200,ready=0(线程:132140387796544)[操作2]await_ready()调用, 返回:0(线程:132140387796544)[操作2]await_suspend()调用, 协程句柄: 0x5e3b8cf602c0(线程:132140387796544)[操作2]await_suspend()返回, 协程即将挂起(线程:132140387796544)[操作1]异步线程结束(线程:132140387796544)[操作2]异步线程开始(线程:132140303910464)[操作2]异步操作完成, 准备恢复协程(线程:132140303910464)[操作2]await_resume()调用, 返回结果:200(线程:132140303910464)第二次 co_await 后继续执行, 结果:200(线程:132140303910464)--- 第三次 co_await(就绪状态)---(线程:132140303910464)[操作3]AsyncOperation 构造:value=300,ready=1(线程:132140303910464)[操作3]await_ready()调用, 返回:1(线程:132140303910464)[操作3]await_resume()调用, 返回结果:300(线程:132140303910464)第三次 co_await 后继续执行, 结果:300(线程:132140303910464)===协程函数执行完成===(线程:132140303910464)promise_type::return_void()调用 - 协程正常返回 promise_type::final_suspend()调用 - 协程结束[操作2]异步线程结束(线程:132140303910464)主线程等待结束(线程:132140388288320)Task 析构, 销毁协程句柄 主函数结束(线程:132140388288320)

一、代码场景与核心目标

示例代码模拟了三个异步操作的执行流程:前两个异步操作需要耗时1秒完成(非就绪状态),第三个操作处于就绪状态无需等待。通过在关键节点打印日志(包括线程ID、函数调用、状态变化),我们能清晰看到协程“暂停-恢复”的完整生命周期,以及协程与线程的交互关系。

二、协程核心组件拆解

要理解C++协程,首先要掌握三个核心组件:可等待对象(Awaitable)协程句柄(coroutine_handle)Promise类型

1. 可等待对象(AsyncOperation)

可等待对象是协程暂停/恢复的核心载体,必须实现三个核心方法(俗称“await三部曲”):

  • await_ready():判断是否需要暂停。返回true时,协程不暂停,直接执行await_resume();返回false时,协程暂停并执行await_suspend()
  • await_suspend(handle):协程暂停时执行的逻辑,接收协程句柄(恢复协程的“钥匙”)。示例中在此启动新线程执行异步操作,耗时1秒后通过handle.resume()恢复协程。
  • await_resume():协程恢复时执行的逻辑,返回异步操作的结果(示例中返回value)。

从日志中能清晰看到非就绪对象(操作1、操作2)和就绪对象(操作3)的执行差异:

// 操作3(就绪状态)的执行日志 [操作3] await_ready() 调用, 返回: 1 [操作3] await_resume() 调用, 返回结果: 300

操作3的await_ready()返回true,跳过await_suspend()直接执行await_resume(),协程无暂停。

2. Promise类型(Task::promise_type)

每个协程都必须关联一个Promise对象,它负责协程的创建、暂停策略、返回值/异常处理。示例中Promise类型实现了5个关键方法:

  • get_return_object():创建协程的返回对象(示例中为Task),协程启动时第一个被调用。
  • initial_suspend():协程启动后是否立即暂停。示例返回std::suspend_never,表示协程启动后立即执行。
  • final_suspend():协程执行完毕后是否暂停。示例返回std::suspend_always,确保协程结束后不立即销毁,等待句柄手动销毁。
  • return_void():协程无返回值时的收尾逻辑(对应co_return或协程正常结束)。
  • unhandled_exception():处理协程执行中未捕获的异常。

3. 协程句柄(coroutine_handle)

协程句柄是操作协程的“手柄”,可以理解为指向协程执行上下文的指针。核心作用是:

  • 通过handle.resume()恢复暂停的协程;
  • 通过handle.destroy()销毁协程的执行上下文(避免内存泄漏);
  • handle.address()可获取句柄地址,用于日志追踪。

示例中Task类封装了句柄,并在析构函数中调用handle.destroy(),保证协程资源正确释放。

三、从日志看协程完整执行流程

结合日志的时间线和线程ID,我们可以把协程执行分为6个关键阶段:

阶段1:协程创建与初始化

主函数开始 (线程: 132140388288320) ********** 创建协程任务 ********** (线程: 132140388288320) promise_type::get_return_object() 调用 Task 对象构造, 句柄: 0x5e3b8cf602c0 promise_type::initial_suspend() 调用 - 立即开始执行 === 协程函数开始执行 === (线程: 132140388288320)
  • 调用detailed_async_example()(协程函数)时,首先创建promise_type对象;
  • 执行get_return_object()创建Task对象,封装协程句柄;
  • initial_suspend()返回std::suspend_never,协程不暂停,立即进入函数体执行。

阶段2:第一次co_await(协程暂停)

--- 第一次 co_await --- (线程: 132140388288320) [操作1] AsyncOperation 构造: value=100, ready=0 (线程: 132140388288320) [操作1] await_ready() 调用, 返回: 0 (线程: 132140388288320) [操作1] await_suspend() 调用, 协程句柄: 0x5e3b8cf602c0 (线程 : 132140388288320) [操作1] await_suspend() 返回, 协程即将挂起 (线程: 132140388288320) 协程任务已创建,控制权返回主函数 (线程: 132140388288320)
  • co_await AsyncOperation{100, false, "操作1"}触发可等待对象的构造;
  • await_ready()返回false,协程需要暂停;
  • await_suspend()启动新线程执行异步操作,随后协程暂停,控制权返回主函数。

阶段3:异步操作执行与协程恢复

主线程等待 3 秒... (线程: 132140388288320) [操作1] 异步线程开始 (线程: 132140387796544) [操作1] 异步操作完成, 准备恢复协程 (线程: 132140387796544) [操作1] await_resume() 调用, 返回结果: 100 (线程: 132140387796544) 第一次 co_await 后继续执行, 结果: 100 (线程: 132140387796544)
  • 主线程进入等待,异步线程(132140387796544)执行耗时操作;
  • 异步操作完成后,调用handle.resume()恢复协程,协程从暂停点继续执行;
  • await_resume()返回结果,协程执行后续逻辑(打印结果)。

阶段4:第二次co_await(重复暂停-恢复)

第二次co_await的流程与第一次完全一致:协程在异步线程中暂停,新的异步线程执行操作后恢复协程,核心差异是恢复后的执行线程变为新的异步线程(132140303910464)。

阶段5:第三次co_await(无暂停)

[操作3] await_ready() 调用, 返回: 1 (线程: 132140303910464) [操作3] await_resume() 调用, 返回结果: 300 (线程: 132140303910464) 第三次 co_await 后继续执行, 结果: 300 (线程: 132140303910464)

await_ready()返回true,协程不暂停,直接执行await_resume()并获取结果,体现了可等待对象“按需暂停”的灵活性。

阶段6:协程收尾与资源释放

=== 协程函数执行完成 === (线程: 132140303910464) promise_type::return_void() 调用 - 协程正常返回 promise_type::final_suspend() 调用 - 协程结束 主线程等待结束 (线程: 132140388288320) Task 析构, 销毁协程句柄
  • 协程函数执行完毕后,调用return_void()完成收尾;
  • final_suspend()暂停协程,等待句柄销毁;
  • Task对象析构时调用handle.destroy(),释放协程的执行上下文。

四、关键知识点总结

  1. 协程与线程的关系:协程本身不绑定线程,暂停的协程可以在任意线程中恢复(示例中协程分别在主线程、操作1线程、操作2线程中执行),这是协程轻量级的核心原因;
  2. 可等待对象的核心作用:通过await_ready()决定是否暂停,await_suspend()处理暂停逻辑(如启动异步操作),await_resume()返回结果,是协程与异步操作的桥梁;
  3. Promise类型的职责:管理协程的生命周期,包括创建返回对象、控制初始/最终暂停策略、处理返回值和异常,是协程的“幕后管理者”;
  4. 资源管理:协程句柄需手动销毁(示例中在Task析构函数中完成),否则会导致内存泄漏。

五、协程的优势与适用场景

对比传统的回调函数、线程池,协程的优势在于:

  • 代码可读性:异步逻辑以“同步写法”实现,避免回调嵌套(回调地狱);
  • 性能:协程暂停/恢复的开销远低于线程切换,适合高并发场景;
  • 灵活性:可按需暂停/恢复,适配异步IO、定时任务、生成器等场景。

C++20协程虽上手有一定门槛,但掌握其核心组件和执行流程后,能显著简化异步编程的复杂度。本文的实战日志为理解协程提供了直观视角,建议结合代码调试,观察每一步的执行顺序和线程变化,加深对协程的理解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/24 13:59:15

Jimeng LoRA保姆级教程:文件夹自动扫描+safetensors识别+自然排序配置

Jimeng LoRA保姆级教程&#xff1a;文件夹自动扫描safetensors识别自然排序配置 1. 项目简介 今天给大家介绍一个特别实用的工具——Jimeng LoRA测试系统。如果你正在训练LoRA模型&#xff0c;或者需要测试不同训练阶段的模型效果&#xff0c;这个工具能帮你节省大量时间。 …

作者头像 李华
网站建设 2026/4/18 9:27:41

零基础玩转SDPose-Wholebody:一键部署全身姿态检测模型

零基础玩转SDPose-Wholebody&#xff1a;一键部署全身姿态检测模型 1. 项目概述 SDPose-Wholebody是一个基于扩散先验技术的全身姿态估计模型&#xff0c;能够精准检测人体133个关键点。这个模型特别适合想要快速上手人体姿态检测的初学者&#xff0c;因为它提供了完整的Dock…

作者头像 李华
网站建设 2026/4/22 0:45:37

通义千问轻量模型:开发者API文档检索效率提升35%

通义千问轻量模型&#xff1a;开发者API文档检索效率提升35% 1. 引言&#xff1a;开发者每天浪费在找文档上的时间 如果你是一名开发者&#xff0c;下面这个场景你一定不陌生&#xff1a;为了调用一个API&#xff0c;你需要先找到它的官方文档。你打开搜索引擎&#xff0c;输…

作者头像 李华
网站建设 2026/4/25 12:09:42

深度学习项目训练环境:快速搭建与实战应用

深度学习项目训练环境&#xff1a;快速搭建与实战应用 你是否经历过这样的场景&#xff1a;花三天配环境&#xff0c;调两天报错&#xff0c;跑一小时显存溢出&#xff0c;最后发现是CUDA版本和PyTorch不兼容&#xff1f;或者刚下载完代码&#xff0c;却卡在“ModuleNotFoundE…

作者头像 李华
网站建设 2026/4/22 6:27:15

Qwen2.5-0.5B实测:轻量级模型的强大对话能力展示

Qwen2.5-0.5B实测&#xff1a;轻量级模型的强大对话能力展示 1. 引言 在AI大模型快速发展的今天&#xff0c;很多人都有一个疑问&#xff1a;小参数模型真的能用吗&#xff1f;0.5B参数的模型能做什么&#xff1f;今天我们就来实测阿里巴巴最新推出的Qwen2.5-0.5B-Instruct模…

作者头像 李华