C++协程从入门到精通

article/2025/8/15 4:11:03

文章目录

    • 一、C++协程入门知识
      • (一)基本概念
      • (二)特点
      • (三)应用场景
    • 二、C++协程精通知识
      • (一)高级特性
      • (二)优化技巧
      • (三)错误处理机制
      • (四)调试技巧

一、C++协程入门知识

(一)基本概念

协程(coroutine)是一种特殊的函数,它可以被暂停(suspend)、恢复执行(resume),并且一个协程可以被多次调用。C++中的协程属于stackless协程,即协程被suspend时不需要堆栈。C++20开始引入协程,围绕协程实现的相应组件较多,如co_wait、co_return、co_yield,promise,handle等组件,灵活性高,组件之间的关系也略复杂,这使得C++协程学习起来有一定难度。

协程与传统函数不同,普通函数是线程相关的,函数的状态跟线程紧密关联;而协程是线程无关的,它的状态与任何线程都没有关系。普通函数调用时,线程的栈上会记录函数的状态(参数、局部变量等),通过移动栈顶指针来完成;而协程的状态是保存在堆内存上的。当协程执行时,它跟普通函数一样依赖线程栈,但一旦暂停,其状态会独立保存在堆中,调用它的线程可以继续做其他事情,下次恢复执行时,协程可以由上次执行的线程执行,也可以由另外一个完全不同的线程执行。

(二)特点

  1. 非阻塞:协程可以在执行过程中暂停,允许其他协程运行,从而实现非阻塞的异步编程。
  2. 轻量级:协程的创建和切换开销较小,适合高并发场景。与传统的多线程相比,协程的创建和切换不需要操作系统的调度,开销远小于线程,并且可以在单个线程中实现高并发,避免了线程上下文切换的开销。
  3. 可读性高:使用协程可以使异步代码更易于理解和维护,避免了回调地狱(callback hell)。协程允许开发者以同步的编码风格编写异步代码,提高了代码的可读性和可维护性。

(三)应用场景

  1. 异步编程:C++20协程在异步编程中的应用非常广泛,它使得编写异步代码变得更加直观和简洁。可以使用co_await来等待异步操作的完成,而不需要使用回调函数或者Promise/Future模式。例如:
#include <iostream>
#include <coroutine>struct Task {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 asynchronous_code() {// 启动一个异步操作// 这里简单模拟,实际中可能是一个耗时的异步函数co_await std::suspend_always{};// 在异步操作完成之后,接着运行下面的代码std::cout << "Asynchronous operation completed." << std::endl;
}int main() {auto task = asynchronous_code();// 这里需要手动处理协程的恢复等操作,实际中可能会有更完善的调度机制return 0;
}
  1. 生成器:C++20协程也可以用来创建生成器,这些生成器可以在每次请求时生成新的值。可以创建一个在请求新值时才计算它们的无限序列。例如:
#include <iostream>
#include <coroutine>// 定义生成器类型
template<typename T>
struct Generator {struct promise_type {T current_value;Generator get_return_object() { return {}; }std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }std::suspend_always yield_value(T value) {current_value = value;return {}; }void return_void() {} };bool move_next() {// 恢复协程执行handle.resume();return !handle.done();}T current_value() {return handle.promise().current_value;}std::coroutine_handle<promise_type> handle;
};// 生成整数序列的生成器协程
Generator<int> integers(int start = 0) {int i = start;while (true) {co_yield i++;}
}int main() {auto gen = integers();for (int i = 0; i < 5; ++i) {if (gen.move_next()) {std::cout << gen.current_value() << std::endl;}}return 0;
}
  1. 并发与并行编程:C++20协程能很好地处理并发和并行编程。通过协程,可以在不阻塞线程的情况下等待操作完成,这在处理I/O操作或者网络请求时尤其有用。例如,在处理多个文件下载任务时:
#include <iostream>
#include <vector>
#include <coroutine>
#include <future>// 模拟异步下载文件的函数
std::future<void> download_file(const std::string& url) {return std::async([url]() {// 模拟下载耗时std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Downloaded: " << url << std::endl;});
}struct Task {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 download_files(const std::vector<std::string>& urls) {std::vector<std::future<void>> tasks;for (const auto& url : urls) {tasks.push_back(download_file(url));}for (auto& task : tasks) {co_await std::suspend_always{};task.wait();}
}int main() {std::vector<std::string> urls = {"url1", "url2", "url3"};auto task = download_files(urls);return 0;
}

二、C++协程精通知识

(一)高级特性

  1. 协程的状态机实现:当一个函数被声明为协程时,编译器会自动将其转换为一个状态机。状态机负责保存协程的执行状态,并在协程挂起和恢复时进行状态切换。状态机通常包含协程的局部变量、挂起点以及promise_type对象等信息。例如:
#include <iostream>
#include <coroutine>struct ReturnObject { struct promise_type { ReturnObject get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() {} void return_void() {} }; 
}; ReturnObject simple_coroutine() { std::cout << "Coroutine started" << std::endl; co_await std::suspend_always{}; std::cout << "Coroutine resumed" << std::endl; 
} int main() { auto coro = simple_coroutine(); // 这里需要手动处理协程的恢复等操作,实际中可能会有更完善的调度机制return 0; 
}

在这个例子中,simple_coroutine函数被编译器转换为状态机,当遇到co_await std::suspend_always{}时,协程挂起,保存当前状态,等待后续恢复执行。
2. 自定义Promise对象和Awaitable对象
- Promise对象promise_type是一个用户自定义的类型,用于控制协程的行为。每个协程都需要定义一个promise_type,它负责创建协程的初始状态、在协程挂起时保存状态、在协程恢复时恢复状态、处理协程的返回值或异常以及控制协程的生命周期。例如:

#include <iostream>
#include <coroutine>struct MyTask { struct promise_type { MyTask get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() { std::terminate(); } void return_void() {} }; 
}; MyTask my_coroutine() { std::cout << "My coroutine started" << std::endl; co_await std::suspend_always{}; std::cout << "My coroutine resumed" << std::endl; 
} int main() { auto task = my_coroutine(); // 这里需要手动处理协程的恢复等操作,实际中可能会有更完善的调度机制return 0; 
}
- **Awaitable对象**:`awaitable`对象用于表示一个可以挂起的异步操作。当协程遇到`co_await`表达式时,它会检查`awaitable`对象是否已经完成。如果未完成,协程将挂起,直到`awaitable`对象完成。`awaitable`对象必须提供`await_ready()`、`await_suspend()`和`await_resume()`等成员函数。例如:
#include <iostream>
#include <coroutine>
#include <future>struct AwaitableFuture { std::future<int> future; bool await_ready() const { return future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } void await_suspend(std::coroutine_handle<> handle) { std::thread([this, handle]() mutable { future.wait(); handle.resume(); }).detach(); } int await_resume() { return future.get(); } 
}; std::future<int> fetchDataAsync() { return std::async([]() { std::this_thread::sleep_for(std::chrono::seconds(2)); return 42; }); 
} int asyncFetchData() { AwaitableFuture af{fetchDataAsync()}; std::cout << "Waiting for data..." << std::endl; int data = co_await af; std::cout << "Data received: " << data << std::endl; 
} int main() { asyncFetchData(); return 0; 
}
  1. 协程与多线程的交互:在多线程环境下,协程可以与线程协作完成任务。可以将协程任务分配到不同的线程中执行,提高并发性能。例如,使用线程池来调度协程任务:
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <coroutine>// 线程池类
class ThreadPool { 
public: ThreadPool(size_t numThreads) { for (size_t i = 0; i < numThreads; ++i) { threads.emplace_back([this] { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(this->queueMutex); this->condition.wait(lock, [this] { return !this->tasks.empty() || this->stop; }); if (this->stop && this->tasks.empty()) return; task = std::move(this->tasks.front()); this->tasks.pop(); } task(); } }); } } ~ThreadPool() { { std::unique_lock<std::mutex> lock(queueMutex); stop = true; } condition.notify_all(); for (std::thread &thread : threads) { thread.join(); } } template<class F> void enqueue(F&& f) { { std::unique_lock<std::mutex> lock(queueMutex); if (stop) throw std::runtime_error("enqueue on stopped ThreadPool"); tasks.emplace(std::forward<F>(f)); } condition.notify_one(); } private: std::vector<std::thread> threads; std::queue<std::function<void()>> tasks; std::mutex queueMutex; std::condition_variable condition; bool stop = false; 
}; // 协程任务
struct Task { 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_task() { std::cout << "Coroutine task started on thread: " << std::this_thread::get_id() << std::endl; co_await std::suspend_always{}; std::cout << "Coroutine task resumed on thread: " << std::this_thread::get_id() << std::endl; 
} int main() { ThreadPool pool(2); pool.enqueue([] { auto task = coroutine_task(); // 这里需要手动处理协程的恢复等操作,实际中可能会有更完善的调度机制}); return 0; 
}

(二)优化技巧

  1. 减少不必要的co_await:频繁的协程切换会带来一定的性能损耗,因此要仔细检查代码,避免在不需要异步操作的地方使用co_await。例如,如果一个函数内部的操作都是同步的,就没必要将其声明为协程。
  2. 批量处理:如果需要执行大量的异步操作,尽量将它们批量处理,减少协程切换的次数。例如,一次性读取多个文件块,而不是每次读取一个。示例代码如下:
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <coroutine>// 模拟异步处理数据的函数
struct Task { 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 process_data_batch(const std::vector<std::string>& data) { // 模拟处理数据for (const auto& line : data) { std::cout << "Processing: " << line << std::endl; } co_return; 
} Task process_files(const std::vector<std::string>& filenames) { const size_t BATCH_SIZE = 10; for (const auto& filename : filenames) { std::ifstream file(filename); std::vector<std::string> buffer; std::string line; while (std::getline(file, line)) { buffer.push_back(line); if (buffer.size() >= BATCH_SIZE) { co_await process_data_batch(buffer); buffer.clear(); } } if (!buffer.empty()) { co_await process_data_batch(buffer); } } 
} int main() { std::vector<std::string> filenames = {"file1.txt", "file2.txt"}; auto task = process_files(filenames); return 0; 
}
  1. 使用高效的调度器:不同的协程库提供了不同的调度器实现,选择一个适合应用场景的调度器,可以显著提升性能。例如,libco库的调度器就非常高效。
  2. 协程池:如果需要频繁创建和销毁协程,可以考虑使用协程池来复用协程对象,减少内存分配和释放的开销。
  3. 内存分配优化:协程在执行过程中,可能会频繁地分配和释放小块内存,导致内存碎片,降低内存的利用率。可以采用内存池等技术来优化内存分配,减少内存碎片的产生。

(三)错误处理机制

  1. 异常处理:在协程中,可以使用try-catch块来捕获和处理异常。当协程中抛出异常时,会调用promise_typeunhandled_exception()方法。例如:
#include <iostream>
#include <coroutine>struct MyTask { struct promise_type { MyTask get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() { std::cout << "Exception occurred in coroutine." << std::endl; } void return_void() {} }; 
}; MyTask my_coroutine() { try { throw std::runtime_error("An error occurred"); } catch (...) { throw; } co_return; 
} int main() { auto task = my_coroutine(); return 0; 
}
  1. 错误传播和恢复策略:当协程中出现错误时,需要考虑错误的传播和恢复策略。可以将错误信息传递给调用者,或者在协程内部进行恢复处理。例如,在一个协程链中,如果某个协程出现错误,可以将错误信息返回给上一级协程进行处理。

(四)调试技巧

  1. 日志记录:在协程中添加日志记录,输出关键步骤和变量的值,有助于定位问题。可以使用标准库的std::cout或者第三方日志库来记录日志。
  2. 调试工具:使用调试工具(如GDB)来调试协程代码。可以设置断点,单步执行代码,查看变量的值和协程的状态。
  3. 代码审查:仔细审查协程代码,检查是否存在逻辑错误、资源泄漏等问题。特别是在处理协程的生命周期和异常处理时,要确保代码的正确性。

综上所述,C++协程是一种强大的异步编程工具,通过深入学习其入门和精通知识,可以更好地利用协程来提高代码的性能和可维护性。


http://www.hkcw.cn/article/gltuQajlpt.shtml

相关文章

蓝桥杯第十六届c组c++题目及个人理解

本篇文章只是部分题目的理解&#xff0c;代码和思路仅供参考&#xff0c;切勿当成正确答案&#xff0c;欢迎各位小伙伴在评论区与博主交流&#xff01; 目录 题目&#xff1a;2025 题目解析 核心提取 代码展示 题目&#xff1a;数位倍数 题目解析 核心提取 代码展示 …

C++日新月异的未来代码:C++11(上)

文章目录 1.统一的列表初始化1.1 普通{ }初始化1.2 initializer_list 2.声明2.1 auto、nullptr2.2 decltype 3.左值右值3.1 概念3.2 左值引用与右值引用比较3.3 左值引用与右值引用的应用3.4 完美转发 希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xf…

C++从入门到实战(十二)详细讲解C++如何实现内存管理

C从入门到实战&#xff08;十二&#xff09;详细讲解C如何实现内存管理 前言一、C内存管理方式1. new/delete操作内置类型2. 异常与内存管理的联系&#xff08;简单了解&#xff09;3. new和delete操作自定义类型 二、 operator new与operator delete函数&#xff08;重点&…

【2025年最新版】Java JDK安装、环境配置教程 (图文非常详细)

文章目录 【2025年最新版】Java JDK安装、环境配置教程 &#xff08;图文非常详细&#xff09;1. JDK介绍2. 下载 JDK3. 安装 JDK4. 配置环境变量5. 验证安装6. 创建并测试简单的 Java 程序6.1 创建 Java 程序&#xff1a;6.2 编译和运行程序&#xff1a;6.3 在显示或更改文件的…

【Linux系统】从 C 语言文件操作到系统调用的核心原理

文章目录 前言lesson 15_基础IO一、共识原理二、回顾C语言接口2.1 文件的打开操作2.2 文件的读取与写入操作2.3 三个标准输入输出流 三、过渡到系统&#xff0c;认识文件系统调用3.1 open 系统调用1. 比特位标志位示例 3.2 write 系统调用1. 模拟实现 w 选项2. 模拟实现 a 选项…

JavaSwing之--JTextField

JavaSwing之–JTextField JTextField 是一个允许编辑单行文本的轻量级组件&#xff0c;它提供了一系列的构造方法和常用方法用来编写可以存储文本的文本框满足程序功能的需求。 以下在简要介绍常用构造方法、普通方法后详解各种方法的应用及举例。 一、构造方法 方法名称功…

Windows系统之VHD安装

环境准备 工具说明Dism部署系统、提取和转换系统镜像等等&#xff0c;还有很多功能大家可以自行探索。这里只用到Dism的部署系统功能。 Releases Chuyu-Team/Dism-Multi-language GitHubbcdedit.exe自带工具 C:\Windows\System32\bcdedit.exe 创建虚拟磁盘 首先右键点击我…

解决Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field ‘com.sun.tools.javac.tre

问题描述 在更新自建基础项目过程中&#xff0c;compile、install报错。 Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualid 解决方案 问题原因是Lombok &#xff0c;与 JDK 21 兼容的最低 Lombok 版本是…

【C++】二叉搜索树 - 从基础概念到代码实现

&#x1f4cc; 个人主页&#xff1a; 孙同学_ &#x1f527; 文章专栏&#xff1a;C &#x1f4a1; 关注我&#xff0c;分享经验&#xff0c;助你少走弯路 文章目录 1. 二叉搜索树的概念2. 二叉搜索树的性能分析3. 二叉搜索树的插入4. 二叉搜素树的查找5. 二叉搜索树的删除6.二…

C++之类和对象基础

⾯向对象三⼤特性&#xff1a;封装、继承、多态 类和对象 一.类的定义1. 类的定义格式2.类域 二.实例化1.对象2.对象的大小 三.this指针 在 C 的世界里&#xff0c;类和对象构成了面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;的核心框架&…

报错java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not ...解决方法

在运行项目时出现java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualidzz这样的报错 解决方法 1.第一步&#xff1a;在pom文件中将lombok的版本改成最新的 此时1.18.34是新…

2025-03-12 Python深度学习1——安装Anaconda与PyTorch库

文章目录 1 配置 Anaconda1.1 下载1.2 安装1.3 配置环境变量1.4 检查安装 2 安装 PyTorch 库2.1 创建 DL 环境2.2 安装/升级 CUDA2.3 配置环境变量2.4 安装 Pytorch 库方法一&#xff08;不稳定&#xff09;方法二&#xff08;推荐&#xff09; 2.5 检查安装 3 Pycharm Communi…

C++ 关联式容器:map,multimap,set,multiset

目录 引言 一、关联式容器概述 1.1 与序列式容器的区别 1.2 底层结构 二、set容器详解set介绍 2.1 set的特性 2.2 set的模板参数 2.3 set的常用接口 2.4 set使用示例 三、map容器详解map介绍 3.1 map的特性 3.2 map的模板参数 3.3 map的常用接口 3.4 map使用示例 …

从零开始配置Qt+VsCode环境

从零开始配置QtVsCode环境 文章目录 从零开始配置QtVsCode环境写在前面扩展安装及配置Qt Configure配置 VsCode创建Qt工程VsCodeQMakeMinGwVsCodeQMakeMsvcVsCodeCMakeMinGwVsCodeCMakeMsvcQtCreatorQMakeMinGw->VsCodeQtCreatorQMakeMsvc->VsCodeQtCreatorCMakeMinGw-&g…

Matlab/Simulink - BLDC直流无刷电机仿真基础教程(一) - 三相逆变器的搭建

Matlab/Simulink - BLDC直流无刷电机仿真基础教程&#xff08;一&#xff09; - 三相逆变器的搭建 前言一、BLDC电机六步换相简明控制原理二、Simulink中BLDC电机模块的机械连接三、三相逆变电路的搭建四、仿真参数设置与仿真结果验证五、补充内容参考链接 前言 本系列文章分享…

Lapce:一款用 Rust 编写的快速且强大的代码编辑器

Lapce&#xff08;IPA&#xff1a;/lps/&#xff09;是一个使用纯 Rust 编写的开源代码编辑器。通过利用 OpenGL 渲染 GUI&#xff0c;以及 Rust 提供的性能&#xff0c;采用Xi-Editor的Rope Science设计&#xff0c;可实现闪电般的快速计算。 Stars 数35888Forks 数1113 主要…

SpringBoot启动后初始化的几种方式

目录 一、静态代码块 二、构造方法 三、PostConstruct 四、InitializingBean 接口 五、 Bean 注解中的 initMethod 六、 CommandLineRunner 接口 七、ApplicationRunner 接口 八、EventListener事件 九、SmartInitializingSingleton接口 十、ApplicationListener接口…

【MySQL课程学习】:MySQL安装,MySQL如何登录和退出?MySQL的简单配置

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;MySQL课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 MySQL在Centos 7环境下的安装&#xff1a; 卸载…

Node.js下载安装及环境配置教程(保姆级教程)

一、安装程序 &#xff08;安装包放在文章最后需要的友友可自取哦&#xff09; &#xff08;1&#xff09;下载完成后&#xff0c;双击安装包&#xff0c;开始安装Node.js &#xff08;2&#xff09;此位置可修改为自己的安装路径&#xff0c;修改完后点击next &#xff08;3…

com.mysql.cj.jdbc.exceptions.CommunicationsException Communications link failure 问题解决

前言: 一般这个报错大多是网络原因导致的&#xff0c;确保你不是网络问题再往下看 问题 在一个方法上&#xff08;该方法非常复杂执行时间长&#xff09;加了 Transactional(rollbackFor Exception.class)后出现了如下图所示的错误 解决&#xff1a; 经过排查并非网络问…