当前位置: 首页 > news >正文

C++学习:六个月从基础到就业——C++20:协程(Coroutines)

C++学习:六个月从基础到就业——C++20:协程(Coroutines)

本文是我C++学习之旅系列的第五十篇技术文章,也是第三阶段"现代C++特性"的第十二篇,继续介绍C++20引入的新特性,本篇重点是协程(Coroutines)。查看完整系列目录了解更多内容。

引言

在现代软件开发中,异步编程已经成为处理I/O操作、并发任务和事件驱动系统的关键范式。然而,传统的基于回调或基于Future/Promise的异步编程模型常常导致代码复杂、难以维护,甚至出现所谓的"回调地狱"。C++20引入的协程(Coroutines)提供了一种革命性的方法来简化异步编程,使开发者能够以看似同步的方式编写异步代码。

协程是一种可以暂停执行并在之后恢复的函数。与普通函数不同,协程可以在执行过程中让出控制权,并在适当的时机重新获得控制权继续执行。这种能力使得协程特别适合处理异步操作、生成器模式和其他需要控制流灵活转移的场景。

本文将介绍C++20协程的基本概念、工作原理、核心组件以及实际应用场景,帮助你掌握这一强大的新特性,编写更简洁、更高效的异步代码。

目录

  • 协程基础概念
  • 协程的语法与组件
  • 协程的工作原理
  • 构建协程类型
  • 实际应用场景
  • 与现有库的集成
  • 性能考量
  • 最佳实践与陷阱
  • 总结

协程基础概念

什么是协程

协程(Coroutine)是一种特殊的函数,能够在执行过程中暂停并保存当前状态,稍后再从暂停的位置继续执行。这种能力使协程成为处理异步操作的强大工具。

协程的核心特点:

  1. 可暂停执行:协程可以在指定点暂停执行,并让出控制权
  2. 状态保存:暂停时,协程的执行状态(包括局部变量和执行位置)被保存
  3. 可恢复执行:协程可以从上次暂停的位置恢复执行
  4. 多入口多出口:与传统函数的"单入口单出口"不同,协程可以有多个出口(暂停点)和入口(恢复点)

下面是一个简单的协程概念示例(使用伪代码):

协程 generate_sequence() {yield 1;  // 产生值1并暂停yield 2;  // 恢复后产生值2并再次暂停yield 3;  // 恢复后产生值3并再次暂停return;   // 结束协程
}主函数() {generator = generate_sequence();value1 = generator.next();  // 获取1value2 = generator.next();  // 获取2value3 = generator.next();  // 获取3
}

协程与线程的区别

协程和线程都是实现并发的机制,但它们有根本性的区别:

特性协程线程
调度方式协作式调度(自己决定何时让出控制权)抢占式调度(由操作系统调度)
切换开销非常低(通常只涉及少量寄存器)较高(涉及完整的上下文切换)
存储空间共享同一线程的栈空间每个线程有独立的栈空间
并行执行同一时刻只有一个协程在执行(单线程内)可以真正并行执行(多核处理器上)
同步机制通常不需要复杂的同步原语需要互斥锁、条件变量等同步机制

协程的主要优势:

  • 更轻量级,创建和销毁成本低
  • 切换开销小,适合频繁切换的场景
  • 不需要考虑大多数并发问题,简化编程模型
  • 特别适合I/O密集型应用,可大幅提高性能

C++20协程的特点

C++20引入的协程具有以下特点:

  1. 低级机制:C++20提供的是协程的底层基础设施,而非高级抽象
  2. 编译器支持:依赖编译器支持,将协程函数转换为状态机
  3. 可定制性:高度可定制,允许库开发者构建各种高级协程抽象
  4. 零开销抽象:设计目标是提供零开销抽象,不为不使用的特性付出代价
  5. 无栈协程:C++20使用的是无栈协程模型,协程状态存储在堆上而非独立栈上

C++20协程的关键语法元素:

  • co_await:暂停协程执行,等待某个操作完成
  • co_yield:产生一个值并暂停执行
  • co_return:完成协程执行并返回一个值

一个协程在C++20中的标志是使用了以上任一关键字。

协程的语法与组件

co_await表达式

co_await是C++20协程的核心操作,用于暂停协程执行,等待某个操作完成,然后恢复执行:

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>// 一个简单的可等待对象
struct SimpleAwaiter {bool await_ready() const noexcept {return false;  // 表示需要暂停协程}void await_suspend(std::coroutine_handle<> handle) const noexcept {// 模拟异步操作,2秒后恢复协程std::thread([handle]() {std::this_thread::sleep_for(std::chrono::seconds(2));handle.resume();  // 恢复协程执行}).detach();}int await_resume() const noexcept {return 42;  // 返回给co_await表达式的结果}
};// 一个简单的协程返回类型
struct SimpleTask {struct promise_type {SimpleTask get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}};
};// 使用co_await的简单协程
SimpleTask simple_coroutine() {std::cout << "协程开始执行" << std::endl;std::cout << "协程即将暂停..." << std::endl;int result = co_await SimpleAwaiter{};  // 在这里暂停协程std::cout << "协程恢复执行,收到结果: " << result << std::endl;std::cout << "协程执行完毕" << std::endl;
}

co_await表达式的处理流程:

  1. 调用等待对象的await_ready()方法
  2. 如果返回true,直接继续执行协程
  3. 如果返回false,暂停协程执行并调用等待对象的await_suspend(handle)方法
  4. 当协程需要恢复时,调用协程句柄的resume()方法
  5. 协程恢复后,调用等待对象的await_resume()方法获取结果

co_yield表达式

co_yield用于从协程产生值并暂停执行,特别适用于实现生成器模式:

#include <iostream>
#include <coroutine>// 简化的生成器实现
template<typename T>
class Generator {
public:struct promise_type {T 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 {}; }std::suspend_always yield_value(T val) {value = val;return {};}void return_void() {}void unhandled_exception() { std::terminate(); }};// 生成器方法bool next() {if (handle && !handle.done()) {handle.resume();return !handle.done();}return false;}T value() const { return handle.promise().value; }// 资源管理~Generator() { if (handle) handle.destroy(); }Generator(Generator&& other) noexcept : handle(other.handle) { other.handle = {}; }Generator& operator=(Generator&&) = delete;Generator(const Generator&) = delete;private:explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 使用co_yield的生成器协程
Generator<int> fibonacci(int n) {if (n > 0) co_yield 0;if (n > 1) co_yield 1;int a = 0, b = 1;for (int i = 2; i < n; ++i) {int next = a + b;co_yield next;a = b;b = next;}
}

co_yield表达式在底层被转换为co_await表达式:

  • co_yield x 基本等同于 co_await promise.yield_value(x)
  • 允许协程产生一个值并暂停,等待下一次恢复再继续执行

co_return语句

co_return用于结束协程执行并指定返回值:

#include <iostream>
#include <coroutine>
#include <utility>// 简单的返回值协程
template<typename T>
class Task {
public:struct promise_type {T result;Task get_return_object() {return Task{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_never initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }void return_value(T value) {result = std::move(value);}void unhandled_exception() { std::terminate(); }};T result() const { return handle.promise().result; }bool is_ready() const { return handle && handle.done(); }// 资源管理~Task() { if (handle) handle.destroy(); }// ... 其他移动构造等private:explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 使用co_return的协程
Task<std::string> compute_greeting(std::string name) {std::string greeting = "Hello, " + name + "!";co_return greeting;  // 结束协程并返回结果
}

co_return的行为取决于Promise类型的实现:

  • co_return; (无值):调用 promise.return_void()
  • co_return x; (有值):调用 promise.return_value(x)
  • 之后协程执行进入最终阶段,调用 co_await promise.final_suspend()

Promise对象

Promise对象是C++20协程的核心组件,用于控制协程的行为。每个协程都关联一个Promise对象,由协程框架在协程创建时构造:

template<typename ReturnType>
struct MyPromise {// 必需的成员ReturnType get_return_object();  // 创建并返回协程的返回对象std::suspend_always initial_suspend();  // 控制协程开始时是否立即执行std::suspend_always final_suspend() noexcept;  // 控制协程结束时的行为// 与返回值相关的成员(二选一)void return_void();  // 处理无返回值的co_return语句void return_value(ReturnType value);  // 处理有返回值的co_return语句// 异常处理成员void unhandled_exception();  // 处理协程中未捕获的异常// 可选的其他成员std::suspend_always yield_value(ValueType value);  // 处理co_yield表达式AwaitableType await_transform(OtherAwaitable a);  // 自定义co_await行为
};

Promise对象的主要职责:

  1. 创建协程的返回对象
  2. 控制协程的初始化和最终化行为
  3. 处理协程的返回值
  4. 处理协程中的异常
  5. 处理co_yield表达式
  6. 自定义co_await表达式的行为

协程句柄

协程句柄(std::coroutine_handle<>)是用于操作协程的非拥有者句柄,提供了与协程交互的接口:

struct MyPromise {struct Task {std::coroutine_handle<MyPromise> handle;~Task() {if (handle) handle.destroy();  // 必须手动销毁协程}};Task get_return_object() {return Task{std::coroutine_handle<MyPromise>::from_promise(*this)};}// ... 其他必需方法
};// 协程句柄操作示例
auto task = some_coroutine();
std::coroutine_handle<MyPromise> handle = task.handle;if (!handle.done()) {handle.resume();  // 恢复协程执行
}// 检查协程是否完成
bool finished = handle.done();

协程句柄的主要操作:

  1. resume():恢复协程执行
  2. done():检查协程是否执行完毕
  3. destroy():销毁协程帧,释放资源
  4. promise():访问关联的Promise对象
  5. address():获取协程帧的地址

重要注意事项:

  • 协程句柄不会自动销毁协程,必须手动调用destroy()
  • 对已完成或已销毁的协程调用resume()会导致未定义行为

协程的工作原理

协程状态

协程在执行过程中可能处于以下几种状态:

  1. 挂起状态(Suspended):协程暂停执行,等待被恢复
  2. 活动状态(Active):协程正在执行中
  3. 完成状态(Done):协程已执行完毕

状态转换:

  • 创建后→初始挂起(根据initial_suspend决定)
  • 挂起→活动:通过resume()恢复执行
  • 活动→挂起:通过co_awaitco_yield暂停
  • 活动→完成:通过co_return或执行到函数末尾
  • 完成→最终挂起(根据final_suspend决定)

协程帧

协程帧是存储协程状态的内存区域,包含以下内容:

  1. Promise对象:控制协程行为的对象
  2. 参数副本:协程参数的拷贝或引用
  3. 局部变量:协程中声明的局部变量
  4. 暂停点信息:当前执行位置的信息
  5. 恢复后要执行的代码地址:恢复执行时的下一条指令地址

协程帧的生命周期:

  1. 协程首次调用时在堆上分配
  2. 协程执行期间持续存在,即使协程暂停
  3. 调用coroutine_handle::destroy()时释放

挂起与恢复机制

协程挂起过程:

  1. 当执行到co_await expr时,首先计算表达式expr得到等待者对象
  2. 调用等待者的await_ready()方法检查是否需要挂起
  3. 如果返回false,保存当前执行状态
  4. 调用等待者的await_suspend(handle)方法,可能安排后续恢复操作
  5. 让出控制权,返回到协程的调用者

协程恢复过程:

  1. 通过协程句柄的resume()方法恢复执行
  2. 恢复协程的执行状态(包括局部变量等)
  3. 调用等待者的await_resume()方法获取结果值
  4. 继续执行剩余的协程代码,直到下一个挂起点或结束

协程的生命周期

一个典型的协程生命周期包括以下阶段:

  1. 创建阶段

    • 分配协程帧
    • 构造Promise对象
    • 调用promise.get_return_object()获取返回对象
    • 调用co_await promise.initial_suspend()决定是否立即挂起
  2. 执行阶段

    • 执行协程函数体
    • co_awaitco_yield点暂停和恢复
    • 处理各种操作和计算
  3. 完成阶段

    • 执行co_return语句或到达函数末尾
    • 调用promise.return_void()promise.return_value(x)
    • 调用co_await promise.final_suspend()决定最终挂起行为
  4. 销毁阶段

    • 调用coroutine_handle::destroy()销毁协程帧
    • 释放所有资源

构建协程类型

基础协程返回类型

创建一个自定义的协程返回类型通常包括以下步骤:

  1. 定义返回类型(比如Task<T>
  2. 定义该类型的promise_type内部类
  3. 实现必要的Promise方法
  4. 提供协程句柄管理机制

下面是一个简化但完整的异步任务类型实现:

template<typename T = void>
class Task {
public:// Promise类型struct promise_type {std::variant<std::monostate, T, std::exception_ptr> result;std::coroutine_handle<> continuation = nullptr;Task get_return_object() {return Task{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_always initial_suspend() { return {}; }auto final_suspend() noexcept {struct FinalAwaiter {bool await_ready() noexcept { return false; }std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> h) noexcept {// 如果有等待的协程,恢复它if (auto cont = h.promise().continuation)return cont;return std::noop_coroutine();}void await_resume() noexcept {}};return FinalAwaiter{};}template<typename U>void return_value(U&& value) {result.template emplace<1>(std::forward<U>(value));}void unhandled_exception() {result.template emplace<2>(std::current_exception());}};// 等待器auto operator co_await() const noexcept {struct TaskAwaiter {std::coroutine_handle<promise_type> handle;bool await_ready() const noexcept { return handle.done(); }std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {handle.promise().continuation = h;return handle;}T await_resume() {auto& result = handle.promise().result;if (result.index() == 2)std::rethrow_exception(std::get<2>(result));if constexpr (!std::is_void_v<T>)return std::get<1>(std::move(result));}};return TaskAwaiter{handle};}// 手动控制方法void start() {if (handle && !handle.done())handle.resume();}bool is_done() const { return !handle || handle.done(); }// 资源管理~Task() { if (handle) handle.destroy(); }Task(Task&& other) noexcept : handle(other.handle) { other.handle = nullptr; }Task& operator=(Task&&) noexcept;  // 实现省略Task(const Task&) = delete;Task& operator=(const Task&) = delete;private:explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 示例协程函数
Task<int> compute() {co_return 42;
}Task<> use_value(int value) {std::cout << "值: " << value << std::endl;co_return;
}Task<> example() {int result = co_await compute();co_await use_value(result);
}

实现生成器

生成器是协程的一个常见应用,用于惰性生成一系列值。以下是一个生成器实现的关键部分:

template<typename T>
class Generator {
public:struct promise_type {std::optional<T> current_value;std::exception_ptr exception;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 {}; }std::suspend_always yield_value(T value) {current_value = std::move(value);return {};}void return_void() {}void unhandled_exception() { exception = std::current_exception(); }};// 迭代方法bool next() {if (handle && !handle.done()) {handle.resume();if (handle.done()) return false;return true;}return false;}T value() const { return *handle.promise().current_value; }// 迭代器支持,使生成器可用于范围for循环class iterator { /* 实现省略 */ };iterator begin();iterator end();// 资源管理~Generator() { if (handle) handle.destroy(); }// ... 移动构造等private:explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 示例:斐波那契数列生成器
Generator<int> fibonacci(int n) {if (n > 0) co_yield 0;if (n > 1) co_yield 1;int a = 0, b = 1;for (int i = 2; i < n; ++i) {int next = a + b;co_yield next;a = b;b = next;}
}// 使用方式
void use_generator() {for (int value : fibonacci(10)) {std::cout << value << " ";  // 输出: 0 1 1 2 3 5 8 13 21 34}
}

自定义等待者

等待者(Awaiter)是实现co_await运算符行为的对象,必须提供三个特定方法:

// 简单的延迟等待器
class Delay {
public:explicit Delay(std::chrono::milliseconds duration) : duration_(duration) {}bool await_ready() const noexcept {return duration_.count() <= 0;}void await_suspend(std::coroutine_handle<> handle) const {std::thread([handle, this]() {std::this_thread::sleep_for(duration_);handle.resume();}).detach();}void await_resume() const noexcept {}private:std::chrono::milliseconds duration_;
};// 使用延迟等待器
Task<> delay_example() {std::cout << "开始" << std::endl;co_await Delay{std::chrono::seconds(1)};std::cout << "1秒后" << std::endl;co_await Delay{std::chrono::milliseconds(500)};std::cout << "再过0.5秒后" << std::endl;
}

自定义等待者需要实现的三个方法:

  1. await_ready(): 决定是否需要暂停协程
  2. await_suspend(handle): 安排何时恢复协程
  3. await_resume(): 提供co_await表达式的结果值

实际应用场景

异步I/O操作

协程特别适合处理异步I/O操作,简化传统的回调模式:

// 模拟异步文件操作
class AsyncFile {
public:AsyncFile(const std::string& filename) : filename_(filename) {std::cout << "打开文件: " << filename << std::endl;}~AsyncFile() {std::cout << "关闭文件: " << filename_ << std::endl;}// 异步读取操作的等待者class ReadOperation {public:ReadOperation(const std::string& filename, int size) : filename_(filename), size_(size) {}bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {// 模拟异步I/O操作std::thread([this, handle]() {std::cout << "异步读取文件: " << filename_ << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(500));// 模拟读取data_ = std::string(size_, 'D');handle.resume();}).detach();}std::string await_resume() { return data_; }private:std::string filename_;int size_;std::string data_;};ReadOperation read(int size) {return ReadOperation(filename_, size);}// 写入操作类似...private:std::string filename_;
};// 异步文件处理协程
Task<> process_file(const std::string& input_file, const std::string& output_file) {try {AsyncFile input(input_file);AsyncFile output(output_file);// 异步读取std::string data = co_await input.read(1024);std::cout << "读取完成,数据大小: " << data.size() << " 字节" << std::endl;// 处理数据std::string processed_data = "处理后: " + data;// 异步写入co_await output.write(processed_data);std::cout << "写入完成" << std::endl;} catch (const std::exception& e) {std::cerr << "错误: " << e.what() << std::endl;}
}

协程在异步I/O中的优势:

  1. 使异步代码看起来像同步代码
  2. 自然的错误处理机制
  3. 资源的自动管理
  4. 避免回调嵌套

并发任务控制

协程可以用于简化并发任务的控制和协调:

// 并发任务执行器
class TaskExecutor {
public:// 提交任务并返回awaitertemplate<typename Func>auto submit(Func&& func) {struct Awaiter {Func func;bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {std::thread([this, handle]() {try {result_ = func();} catch (...) {exception_ = std::current_exception();}handle.resume();}).detach();}auto await_resume() {if (exception_)std::rethrow_exception(exception_);return result_;}std::optional<std::invoke_result_t<Func>> result_;std::exception_ptr exception_;};return Awaiter{std::forward<Func>(func)};}
};// 使用执行器
Task<> parallel_tasks() {TaskExecutor executor;auto task1 = executor.submit([]() {std::this_thread::sleep_for(std::chrono::seconds(1));return 42;});auto task2 = executor.submit([]() {std::this_thread::sleep_for(std::chrono::seconds(2));return std::string("Hello");});// 并发等待两个任务int result1 = co_await task1;std::string result2 = co_await task2;std::cout << "结果: " << result1 << ", " << result2 << std::endl;
}

生成器与惰性计算

协程特别适合实现惰性计算的生成器:

Generator<int> range(int start, int end, int step = 1) {for (int i = start; i < end; i += step) {co_yield i;}
}Generator<std::string> file_lines(const std::string& filename) {std::ifstream file(filename);std::string line;while (std::getline(file, line)) {co_yield line;}
}// 无限序列,按需计算
Generator<int> primes() {std::vector<int> found_primes;co_yield 2;  // 第一个质数for (int n = 3; ; n += 2) {  // 从3开始的奇数bool is_prime = true;for (int prime : found_primes) {if (prime * prime > n) break;  // 优化检查if (n % prime == 0) {is_prime = false;break;}}if (is_prime) {found_primes.push_back(n);co_yield n;}}
}// 使用生成器
void use_generators() {// 使用范围生成器for (int i : range(0, 10, 2)) {std::cout << i << " ";  // 输出 0 2 4 6 8}// 读取文件的前10行int count = 0;for (const auto& line : file_lines("data.txt")) {std::cout << line << std::endl;if (++count >= 10) break;}// 获取前10个质数count = 0;for (int p : primes()) {std::cout << p << " ";if (++count >= 10) break;}
}

状态机实现

协程可以优雅地实现状态机:

// 解析器状态机
Task<std::vector<std::string>> parse_csv(std::string_view data) {std::vector<std::string> fields;std::string current_field;enum class State { Field, QuotedField, QuoteInQuotedField };State state = State::Field;for (char c : data) {// 复杂状态处理逻辑switch (state) {case State::Field:if (c == ',') {fields.push_back(current_field);current_field.clear();co_await Delay{std::chrono::microseconds(1)};  // 允许状态检查} else if (c == '"') {state = State::QuotedField;} else {current_field += c;}break;case State::QuotedField:if (c == '"') {state = State::QuoteInQuotedField;} else {current_field += c;}break;case State::QuoteInQuotedField:if (c == '"') {current_field += '"';state = State::QuotedField;} else if (c == ',') {fields.push_back(current_field);current_field.clear();state = State::Field;co_await Delay{std::chrono::microseconds(1)};  // 允许状态检查} else {current_field += c;state = State::Field;}break;}}// 添加最后一个字段fields.push_back(current_field);co_return fields;
}

与现有库的集成

与std::future的集成

可以将协程与std::future集成,简化异步操作:

// 使future可等待
template<typename T>
auto operator co_await(std::future<T>&& future) {struct FutureAwaiter {std::future<T> 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();}T await_resume() {return future.get();}};return FutureAwaiter{std::move(future)};
}// 示例:使用std::async与协程
Task<> future_example() {auto future = std::async(std::launch::async, []() {std::this_thread::sleep_for(std::chrono::seconds(1));return 42;});std::cout << "等待future..." << std::endl;int result = co_await std::move(future);std::cout << "结果: " << result << std::endl;
}

与异步网络库的结合

协程可以与异步网络库(如Asio)结合,简化网络编程:

// 简化的Asio协程适配器
template<typename R>
auto awaitable(std::function<void(std::function<void(R)>)> async_op) {struct Awaiter {std::function<void(std::function<void(R)>)> async_op;R result;bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {async_op([this, handle](R r) {result = std::move(r);handle.resume();});}R await_resume() { return std::move(result); }};return Awaiter{std::move(async_op)};
}// 假设的异步HTTP客户端
class HttpClient {
public:using Callback = std::function<void(std::string)>;// 异步HTTP GET请求void async_get(const std::string& url, Callback callback) {// 模拟异步HTTP请求std::thread([url, callback = std::move(callback)]() {std::this_thread::sleep_for(std::chrono::seconds(1));std::string response = "Response from " + url;callback(response);}).detach();}
};// 使用协程简化HTTP请求
Task<> fetch_data() {HttpClient client;std::cout << "发起请求..." << std::endl;// 转换为可等待操作std::string response = co_await awaitable<std::string>([&client](auto cb) {client.async_get("https://example.com", std::move(cb));});std::cout << "收到响应: " << response << std::endl;
}

与事件循环的结合

协程可以集成到事件循环中,实现高效的非阻塞异步编程:

// 简化的事件循环
class EventLoop {
public:using Task = std::function<void()>;void post(Task task) {std::lock_guard<std::mutex> lock(mutex_);tasks_.push(std::move(task));has_task_.notify_one();}void run() {while (!stop_) {Task task;{std::unique_lock<std::mutex> lock(mutex_);has_task_.wait(lock, [this] { return !tasks_.empty() || stop_; });if (stop_) break;task = std::move(tasks_.front());tasks_.pop();}task();}}void stop() {std::lock_guard<std::mutex> lock(mutex_);stop_ = true;has_task_.notify_one();}private:std::queue<Task> tasks_;std::mutex mutex_;std::condition_variable has_task_;bool stop_ = false;
};// 事件循环协程适配器
class EventLoopAwaiter {
public:EventLoopAwaiter(EventLoop& loop) : loop_(loop) {}bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {loop_.post([handle]() { handle.resume(); });}void await_resume() {}private:EventLoop& loop_;
};// 使用事件循环执行协程
Task<> event_loop_example(EventLoop& loop) {std::cout << "开始" << std::endl;co_await EventLoopAwaiter(loop);std::cout << "第一次在事件循环中恢复" << std::endl;co_await EventLoopAwaiter(loop);std::cout << "第二次在事件循环中恢复" << std::endl;co_await EventLoopAwaiter(loop);std::cout << "第三次在事件循环中恢复" << std::endl;
}

性能考量

协程的开销

协程相比传统函数有一些额外开销:

  1. 内存分配:协程帧通常在堆上分配,虽然编译器可能优化某些情况
  2. 额外的函数调用:如await_readyawait_suspend等方法的调用
  3. 状态管理:保存和恢复协程状态的开销

但协程通常比其他异步编程方式(如线程)的开销要小得多。

零开销抽象的实现

C++20协程设计为"零开销抽象",遵循C++的"只为你使用的部分付费"原则:

  1. 未使用的协程特性不会产生开销
  2. 编译器可以对协程进行优化,例如:
    • 内联协程函数以减少调用开销
    • 在某些情况下避免堆分配
    • 消除不必要的暂停/恢复操作

优化策略

优化协程性能的几种策略:

  1. 避免不必要的暂停点:比如使用await_ready()返回true避免不必要的暂停

  2. 减少内存分配

    // 针对小协程帧使用固定大小的内存池
    void* operator new(size_t size) {static thread_local std::array<byte, 1024> buffer;static thread_local size_t used = 0;if (size + used <= buffer.size()) {void* result = buffer.data() + used;used += size;return result;}return ::operator new(size);
    }
    
  3. 尽可能使用值类型而非引用计数智能指针,减少动态分配和引用计数操作

  4. 避免协程嵌套过深,每一层嵌套都会创建新的协程帧

最佳实践与陷阱

错误处理

协程中的错误处理策略:

  1. 使用标准的try-catch机制

    Task<> error_handling_example() {try {int result = co_await potentially_throwing_operation();std::cout << "结果: " << result << std::endl;} catch (const std::exception& e) {std::cerr << "捕获到异常: " << e.what() << std::endl;}
    }
    
  2. 通过Promise对象的unhandled_exception方法传播异常

    void unhandled_exception() {exception_ = std::current_exception();
    }
    
  3. 使用错误码和std::expected代替异常,适合性能关键场景

取消操作

实现协程操作的取消机制:

class CancellationToken {
public:bool is_cancelled() const {return cancelled_.load(std::memory_order_acquire);}void cancel() {cancelled_.store(true, std::memory_order_release);}private:std::atomic<bool> cancelled_{false};
};// 在协程中使用取消令牌
Task<> cancellable_operation(std::shared_ptr<CancellationToken> token) {for (int i = 0; i < 10; ++i) {if (token->is_cancelled()) {std::cout << "操作被取消" << std::endl;co_return;}std::cout << "步骤 " << i << std::endl;co_await Delay{std::chrono::milliseconds(500)};}
}

调试协程

调试协程的挑战与技巧:

  1. 打印生命周期事件,跟踪协程的创建、暂停、恢复和销毁
  2. 使用协程上下文信息,在每个暂停点记录状态
  3. 在Promise类型中添加调试辅助方法

例如,添加调试日志:

class DebugPromise {
public:// ... 正常Promise方法auto initial_suspend() {std::cout << "协程开始[" << id_ << "]" << std::endl;return std::suspend_always{};}auto final_suspend() noexcept {std::cout << "协程结束[" << id_ << "]" << std::endl;return std::suspend_always{};}private:static inline std::atomic<int> next_id_ = 0;int id_ = next_id_++;
};

避免的常见错误

使用协程时的常见陷阱:

  1. 协程句柄生命周期管理不当

    // 错误:协程句柄被销毁,但协程尚未完成
    void misuse_handle() {auto handle = some_coroutine().handle;handle.resume();handle.destroy();  // 如果协程还在运行,会导致未定义行为
    }
    
  2. 循环依赖导致内存泄漏:两个协程互相等待对方完成

  3. 忽略异常传播:异常如果未被处理,可能会导致程序终止

  4. 没有处理协程挂起时的资源释放问题

    Task<> resource_leak() {auto resource = acquire_resource();  // 获取资源co_await some_operation();  // 如果挂起,resource可能未被正确释放release_resource(resource);
    }// 更好的方式:使用RAII
    Task<> proper_cleanup() {auto guard = ResourceGuard();  // RAII包装器co_await some_operation();  // 即使挂起,guard的析构函数也会在协程销毁时被调用
    }
    

总结

C++20协程是现代C++中最具革命性的特性之一,它为异步编程提供了一种优雅而高效的解决方案。协程允许我们以看似同步的方式编写异步代码,大大提高了代码的可读性、可维护性和效率。

主要优势包括:

  1. 简化异步编程:使异步代码的结构与同步代码类似,避免回调地狱
  2. 高效的状态管理:无栈协程模型提供轻量级的执行控制转移
  3. 强大的抽象能力:可以封装各种异步操作为协程
  4. 与现有C++特性兼容:与异常处理、RAII、智能指针等特性无缝集成
  5. 灵活的定制性:提供低级机制,允许构建各种高级抽象

协程特别适合以下场景:

  • 异步I/O操作
  • 并发任务控制
  • 生成器与惰性计算
  • 状态机实现
  • 事件驱动编程

虽然C++20协程的使用有一定的学习曲线,但一旦掌握,它将成为我们处理异步编程问题的强大工具。随着越来越多的库和框架提供协程支持,C++协程将在未来的软件开发中发挥越来越重要的作用。

在下一篇文章中,我们将探讨C++20的另一个重要特性:范围(Ranges)库,它如何革新C++的算法和容器交互方式。


这是我C++学习之旅系列的第五十篇技术文章。查看完整系列目录了解更多内容。

相关文章:

  • 【Linux】ELF与动静态库的“暗黑兵法”:程序是如何跑起来的?
  • IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink
  • Ansible模块——文件内容修改
  • 【Linux】简易版Shell实现(附源码)
  • Day29 类的装饰器
  • PopSQL:一个支持团队协作的SQL开发工具
  • 机器学习(12)——LGBM(1)
  • 软件架构之--论微服务的开发方法1
  • 一种开源的高斯泼溅实现库——gsplat: An Open-Source Library for Gaussian Splatting
  • Leetcode 3553. Minimum Weighted Subgraph With the Required Paths II
  • EMQX开源版安装指南:Linux/Windows全攻略
  • 初学c语言15(字符和字符串函数)
  • 【图像生成大模型】Wan2.1:下一代开源大规模视频生成模型
  • windows笔记本连接RKNN3588网络配置解析
  • jvm安全点(四)openjdk17 c++源码垃圾回收之安全点轮询页内存设置不可访问
  • 李臻20242817_安全文件传输系统项目报告_第12周
  • 【Java ee初阶】jvm(3)
  • ARM A64 STR指令
  • Java大厂面试:从Web框架到微服务技术的场景化提问与解析
  • Java基础知识总结(超详细整理)
  • 经济日报:政府采购监管篱笆要扎得更牢
  • 央媒:设施老化、应急预案套模板,养老机构消防隐患亟待排查
  • 泽连斯基与美国副总统及国务卿会谈,讨论伊斯坦布尔谈判等问题
  • 广西桂林、百色、河池等地表态:全力配合中央对蓝天立的审查调查
  • 蔡建忠已任昆山市副市长、市公安局局长
  • 光明日报社副总编辑薄洁萍调任求是杂志社副总编辑