C++中的协程是一种可以暂停执行并在后续恢复的特殊函数,它不属于传统的线程调度模型,而是由程序自身控制执行流程,相比线程更加轻量,适合处理异步、延迟执行等场景。C++20标准正式将协程纳入语言特性,提供了一套原生的协程语法支持。
C++协程的核心概念
传统函数在调用时会从开始执行到结束返回,中间无法暂停。而协程可以在执行过程中主动暂停,保存当前的执行状态,之后在合适的时机从暂停的位置继续执行。这种特性让协程非常适合处理需要等待外部资源的场景,比如网络请求、文件读写等,不需要阻塞当前线程,提升程序的并发处理能力。
和普通函数相比,C++协程有以下几个典型特征:
- 函数体内可以使用
co_await、co_yield、co_return三个关键字,只要使用了其中一个,函数就会被编译器识别为协程 - 协程的返回类型不是普通的类型,需要满足协程返回类型的约定,包含特定的成员函数或者类型定义
- 协程暂停时会保存当前的局部变量、指令指针等状态,恢复时可以完整还原执行上下文
C++20协程的关键语法
三个核心关键字
C++20为协程提供了三个专用关键字,分别承担不同的作用:
- co_await:用于暂停当前协程的执行,等待一个可等待对象(awaitable)的结果,等待完成后再恢复协程继续执行
- co_yield:用于暂停协程并返回一个值给调用方,调用方可以后续再次恢复协程执行,常用于生成器场景
- co_return:用于协程执行结束返回最终结果,作用和普通函数的return类似,但只能用于协程中
协程返回类型的要求
协程的返回类型不能是int、void这类普通类型,需要满足一定的约定。简单来说,返回类型需要包含以下部分:
- 一个
promise_type内部类型,用于协程状态的创建和管理 promise_type需要定义get_return_object、initial_suspend、final_suspend、return_void或return_value等成员函数,控制协程的初始化、暂停、结束等行为
下面是一个简单的返回类型定义示例:
#include <coroutine>
#include <iostream>
// 简单的协程返回类型
struct SimpleCoroutine {
struct promise_type {
SimpleCoroutine get_return_object() {
return SimpleCoroutine{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle;
};
C++20协程基础应用示例
简单协程示例
下面是一个最基础的协程示例,展示协程的暂停和恢复过程:
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>
struct SimpleCoroutine {
struct promise_type {
SimpleCoroutine get_return_object() {
return SimpleCoroutine{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle;
};
// 协程函数
SimpleCoroutine test_coroutine() {
std::cout << "协程开始执行" << std::endl;
co_await std::suspend_always{}; // 暂停协程
std::cout << "协程恢复执行" << std::endl;
co_return;
}
int main() {
auto coro = test_coroutine();
std::cout << "主函数继续执行" << std::endl;
// 恢复协程执行
if (coro.handle && !coro.handle.done()) {
coro.handle.resume();
}
// 销毁协程句柄
if (coro.handle) {
coro.handle.destroy();
}
return 0;
}
上述代码的执行输出顺序为:协程开始执行、主函数继续执行、协程恢复执行。可以看到协程在co_await处暂停后,主函数可以继续执行其他逻辑,之后手动恢复协程才会继续执行后续内容。
生成器场景应用
co_yield关键字非常适合实现生成器,下面的示例实现了一个简单的整数序列生成器:
#include <coroutine>
#include <iostream>
// 生成器返回类型
template<typename T>
struct Generator {
struct promise_type {
T 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 return_void() {}
void unhandled_exception() { std::terminate(); }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
};
std::coroutine_handle<promise_type> handle;
// 迭代器相关定义
struct iterator {
std::coroutine_handle<promise_type> handle;
bool operator!=(const iterator& other) const {
return handle && !handle.done();
}
iterator& operator++() {
handle.resume();
return *this;
}
T operator*() const {
return handle.promise().current_value;
}
};
iterator begin() {
handle.resume();
return iterator{handle};
}
iterator end() {
return iterator{nullptr};
}
};
// 生成0到n-1的序列协程
Generator<int> range(int n) {
for (int i = 0; i < n; ++i) {
co_yield i;
}
}
int main() {
for (int val : range(5)) {
std::cout << val << " ";
}
// 输出:0 1 2 3 4
return 0;
}
协程使用注意事项
在使用C++20协程时需要注意以下几点:
- 协程的返回类型需要严格遵守约定,否则编译器会直接报错,实际开发中可以使用标准库或者第三方库封装好的返回类型,减少重复开发
- 协程的暂停和恢复不会自动进行线程切换,默认还是在当前线程执行,如果需要跨线程调度,需要自定义可等待对象的await_suspend逻辑
- 协程句柄需要手动管理生命周期,使用完成后要及时销毁,避免内存泄漏
- 不是所有编译器都完全支持C++20协程特性,使用前需要确认编译器的支持情况,比如GCC 10+、Clang 14+、MSVC 2019+版本都提供了较好的支持