前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C++协程从入门到精通

C++协程从入门到精通

原创
作者头像
码事漫谈
发布于 2025-05-05 14:14:44
发布于 2025-05-05 14:14:44
1350
举报
文章被收录于专栏: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; }#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; }#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; }
  2. 生成器:C++20协程也可以用来创建生成器,这些生成器可以在每次请求时生成新的值。可以创建一个在请求新值时才计算它们的无限序列。例如:
  3. 并发与并行编程:C++20协程能很好地处理并发和并行编程。通过协程,可以在不阻塞线程的情况下等待操作完成,这在处理I/O操作或者网络请求时尤其有用。例如,在处理多个文件下载任务时:

二、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; }#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; }
  2. 协程与多线程的交互:在多线程环境下,协程可以与线程协作完成任务。可以将协程任务分配到不同的线程中执行,提高并发性能。例如,使用线程池来调度协程任务:

(二)优化技巧

  1. 减少不必要的co_await:频繁的协程切换会带来一定的性能损耗,因此要仔细检查代码,避免在不需要异步操作的地方使用co_await。例如,如果一个函数内部的操作都是同步的,就没必要将其声明为协程。#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; }
  2. 批量处理:如果需要执行大量的异步操作,尽量将它们批量处理,减少协程切换的次数。例如,一次性读取多个文件块,而不是每次读取一个。示例代码如下:
  3. 使用高效的调度器:不同的协程库提供了不同的调度器实现,选择一个适合应用场景的调度器,可以显著提升性能。例如,libco库的调度器就非常高效。
  4. 协程池:如果需要频繁创建和销毁协程,可以考虑使用协程池来复用协程对象,减少内存分配和释放的开销。
  5. 内存分配优化:协程在执行过程中,可能会频繁地分配和释放小块内存,导致内存碎片,降低内存的利用率。可以采用内存池等技术来优化内存分配,减少内存碎片的产生。

(三)错误处理机制

  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; }
  2. 错误传播和恢复策略:当协程中出现错误时,需要考虑错误的传播和恢复策略。可以将错误信息传递给调用者,或者在协程内部进行恢复处理。例如,在一个协程链中,如果某个协程出现错误,可以将错误信息返回给上一级协程进行处理。

(四)调试技巧

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
Day7函数式编程3/3
装饰器 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。 >>> def now(): ... print('2018-3-27') ... >>> f = now >>> f() 2018-3-27 函数对象有一个__name__属性,可以拿到函数的名字: >>> now.__name__ 'now' >>> f.__name__ 'now' 现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码
林清猫耳
2018/04/26
6580
Python神器列传:函数神器functools模块全解析
作者:j_hao104 来源:见文末 functools 模块提供用于调整或扩展函数和其他可调用对象的工具,而无需完全重写它们。 装饰器 partial 类是 functools 模块提供的主要工具, 它可以用来“包装”一个可调用的对象的默认参数。它产生的对象本身是可调用的,可以看作是原生函数。它所有的参数都与原来的相同,并且可以使用额外的位置参数或命名参数来调用。使用 partial 代替 lambda 来为函数提供默认参数,同时保留那些未指定的参数。 Partial 对象 下面列子是对 myfunc
小小科
2018/06/20
1K0
Python学习笔记(四)·函数式编程
函数是 Python 内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
公爵
2022/09/28
9590
Python学习笔记(四)·函数式编程
Python(七)
Python 内建了 map() 函数。 map() 函数接收两个参数,一个是函数,一个是 Iterable,map 将传入的函数依次作用到序列的每个元素,并把结果作为新的 Iterator 返回。由于结果是一个 Iterator,Iterator 是惰性序列,因此我们还可以通过 list() 函数让它把整个序列都计算出来并返回一个 list。
1ess
2021/11/01
2700
Python函数式编程-高阶函数、匿名函数、装饰器、偏函数
本篇文章我们来介绍下Python函数式编程的知识。最主要的一点,Python中的函数是对象,可以复制给变量!好了,我们来介绍几个Python函数式编程中的要点,包括高阶函数、匿名函数、装饰器、偏函数等等。精彩内容,不容错过!
QQ1622479435
2018/10/23
7690
Python学习 Day 5 高阶函数 map/reduce filter sorter 返回函数 匿名函数 装饰器 偏函数
>>> abs(-10) #把abs指向10后,无法通过abs(-10)调用该函数
Ai学习的老章
2019/04/10
3690
Python3.6学习笔记(二)
对于指定索引范围取值的操作,Python提供了slice方法,类似于Excel中数据透视表的切片器。
大江小浪
2018/07/24
4800
Python的functools模块
  元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性:模块名、名称、限定名、文档、参数注解
py3study
2020/01/09
4070
Python函数式编程学习笔记
高阶函数:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
霍格沃兹测试开发Muller老师
2024/05/14
1210
Python 新手突破瓶颈指南:functools.wraps 元数据复制
在 Python 中,装饰器是非常强大的工具,用于修改或扩展函数的行为。然而,使用装饰器时,我们经常会遇到一个问题:被装饰函数的元数据信息(如名称、文档字符串和参数列表)可能会丢失。这时,functools.wraps 就派上了用场。本文将深入探讨 functools.wraps 的作用,并提供一些实际的应用例子。
MegaQi
2024/08/09
3270
Python 新手突破瓶颈指南:functools.wraps 元数据复制
python装饰器1:函数装饰器详解
假如你已经定义了一个函数funcA(),在准备定义函数funcB()的时候,如果写成下面的格式:
py3study
2020/01/19
7380
Python基础(一)
以#开头的语句是注释,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。
haifeiWu
2018/09/11
6830
一文读懂Python 高阶函数
将函数作为参数传入,这样的函数称为高阶函数。函数式编程就是指这种高度抽象的编程范式。 变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。如下所示:
Wu_Candy
2022/07/04
2820
一文读懂Python 高阶函数
python模块之functools
functools模块提供了某些高阶函数(high-order function)。
枇杷李子橙橘柚
2019/05/26
6630
python 高阶函数:Decorato
print log('execute') print log(now) print log('execute')(now)
py3study
2020/01/14
4480
Python进阶——如何实现一个装饰器?
在 Python 开发中,我们经常会看到使用装饰器的场景,例如日志记录、权限校验、本地缓存等等。
_Kaito
2021/03/23
3450
Python 学习入门(31)—— 序列化
Python内置marshal, cPickle等序列化库,但cPickle效率不太理想,marshal文档也说不保证版本兼容性。今天在列表中看到几个第三方库,故自己测试下:
阳光岛主
2019/02/18
3740
python3 装饰器
1111 test now1() 1505878800.4148097 222 now2() 1505878800.4148097
py3study
2020/01/03
3700
python 装饰器
文章目录 1. 装饰器在导入的时候就会执行 2. functools.wraps 装饰器,保持 被装饰的函数的 `__name__` 的值不变 3. functools.lru_cache 实现备忘录 4. functools.singledispatch 处理多个不同的输入类型 5. 堆叠装饰器 6. 参数化装饰器 learn from 《流畅的python》 def deco(func): def inner(): print("running inner()") r
Michael阿明
2021/09/06
4010
Python基础学习(二)
函数是对程序逻辑进行结构化或是过程化的一种编程方法,其是组织好的,可重复使用的,用来实现单一,或者相同功能的代码段。
py3study
2020/01/17
7370
相关推荐
Day7函数式编程3/3
更多 >
LV.3
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档