参考链接:https://www.bennyhuo.com/book/cpp-coroutines/00-foreword.html
源码链接:https://github.com/Cookies-CGQ/Cpp20-Coroutine
C++20协程认识
💡 C++20主要应用场景
异步编程:在处理I/O操作(如网络请求、文件读写)时,使用co_await可以避免线程阻塞,以同步的代码风格实现异步的高性能。
生成器(Generator):需要按需生成或遍历一个序列时(如遍历一个容器或生成无限数列),使用co_yield可以非常简洁地实现,无需手动维护迭代状态。
状态机:对于复杂的状态逻辑,协程可以很直观地表达,每个状态点由挂起和恢复来分隔,使代码更清晰。
⚠️ 重要特性与注意事项
无栈协程:C++20选择的是无栈协程方案。这意味着协程挂起时不会保存整个调用栈,而是将必要状态保存在堆上分配的"协程帧"中。这使得协程非常轻量(内存开销小,切换极快),但一个限制是无法在嵌套函数中任意深度地挂起,挂起只能在最顶层的协程函数中进行。
非对称协程:C++20的协程是非对称的。一个协程挂起后,控制权会固定地返回给直接恢复它的调用者(resume的调用者),而不能随意跳转到其他协程。这与Go语言中对称的goroutine有所不同。
手动内存管理:协程帧的内存管理需要特别注意。通常情况下,需要在协程对象的析构函数中(例如生成器的析构函数)通过coroutine_handle调用.destroy()来显式销毁协程,以避免内存泄漏。
协程的状态
协程体、协程执行、协程挂起、协程恢复、协程异常、协程返回
协程挂起
co_await关键字的操作数是一个 可等待对象 (Awaitable) ,其背后的具体执行逻辑由一个 等待体 (Awaiter) 对象定义
| 概念元素 | 角色说明 |
|---|---|
co_await 运算符 |
关键字,触发挂起和恢复逻辑 |
| 可等待对象 (Awaitable) | co_await 后面的表达式,是操作的直接目标 |
| 等待体 (Awaiter) | 真正定义挂起、恢复行为的对象,包含三个关键方法 |
对于co_await 表达式当中的expr的处理,c++有一套完善的流程:
- 如果 promise_type 当中定义了 await_transform 函数,那么先通过
promise.await_transform(expr)来对 expr 做一次转换,得到的对象称为 awaitable;否则 awaitable 就是 expr 本身。 - 接下来使用 awaitable 对象来获取等待体(awaiter)。如果 awaitable 对象有
operator co_await运算符重载,那么等待体就是operator co_await(awaitable),否则等待体就是 awaitable 对象本身。
例如,如果是co_await (int)i,我们要么给 promise_type 实现一个 await_tranform(int) 函数,要么就为整型实现一个 operator co_await 的运算符重载,二者选一个就可以了。
等待体
等待体需要实现三个函数 ,分别是 await_ready,await_suspend,await_resume,这三个函数在挂起和恢复时分别调用。
1、await_ready函数:
cpp
bool await_ready();
这是协程遇到 co_await 时第一个调用的函数。它的返回值是一个布尔值,用于判断协程是否需要挂起。
- 返回
true:表示所等待的事件或数据已经就绪。此时,协程不会挂起,程序流会跳过await_suspend,直接继续执行后续代码,并调用await_resume来获取结果。这可以避免不必要的挂起开销,是一种性能优化手段。 - 返回
false:表示结果尚未就绪,协程需要被挂起。这是更常见的情况,意味着将启动一个真正的异步操作。
标准库当中提供了两个非常简单直接的等待体,struct suspend_always 表示总是挂起,struct suspend_never 表示总是不挂起。不难想到,这二者的功能主要就是依赖 await_ready 函数的返回值:
cpp
struct suspend_never {
constexpr bool await_ready() const noexcept {
return true; // 返回 true,总是不挂起
}
...
};
struct suspend_always {
constexpr bool await_ready() const noexcept {
return false; // 返回 false,总是挂起
}
...
};
2、await_suspend函数:只有在 await_ready 返回 false 后,此函数才会被调用。此时,协程的局部变量和执行位置(挂起点)等信息都已被安全地保存到协程状态中。这是整个异步流程的核心调度环节。
??? await_suspend(std::coroutine_handle<> coroutine_handle);
它接收一个重要的参数:std::coroutine_handle<> coroutine_handle,这个句柄代表当前被挂起的协程本身。你的主要任务通常包括:
-
保存或传递该句柄:例如,将它绑定到一个异步I/O操作的回调函数中,或传递给另一个工作线程。
-
启动异步操作:然后当前线程就可以继续执行其他任务了。
coroutine_handle.resume();
注意到 await_suspend 函数的返回值类型我们没有明确给出,因为它有以下几种选项:
- 返回 void 类型或者返回 true,表示当前协程挂起之后将执行权还给当初调用或者恢复当前协程的函数。
- 返回 false,则恢复执行当前协程。注意此时不同于 await_ready 返回 true 的情形,此时协程已经挂起,await_suspend 返回 false 相当于挂起又立即恢复。
- 返回其他协程的 coroutine_handle 对象,这时候返回的 coroutine_handle 对应的协程被恢复执行。这是一种链式唤醒机制。当前协程保持挂起,而返回的句柄所对应的另一个协程会被立即恢复执行。这可以用于实现协程之间的直接切换,而无需返回到最外层的调度器。
- 抛出异常,此时当前协程恢复执行,并在当前协程当中抛出异常。
可见,await_suspend 支持的情况非常多,也相对复杂。实际上这也是 C++ 协程当中最为核心的函数之一了。
3、await_resume函数:
当异步操作完成,外部代码通过之前保存的协程句柄调用 .resume() 后,协程会从挂起点恢复执行。恢复后第一件做的事就是调用 await_resume 方法。
await_resume 的返回值类型也是不限定的,返回值将作为 co_await 表达式的返回值。
??? await_resume();
| 函数 | 调用时机 | 核心职责 | 常见返回值/行为 |
|---|---|---|---|
await_ready |
co_await 表达式处首先被调用。 |
决策者:判断是否需要挂起协程。 | true:无需挂起,直接继续。 false:需要挂起,执行 await_suspend。 |
await_suspend |
在 await_ready 返回 false,协程状态保存完毕后调用。 |
调度者:挂起期间安排异步操作,并决定后续流程。 | void/true:成功挂起。 false:立即恢复。 另一协程句柄:恢复指定协程。 |
await_resume |
当协程恢复执行时(在挂起点之后立即调用)。 | 交付者 :提供 co_await 表达式的结果,或清理异步操作。 |
返回值作为整个 co_await 表达式的结果。 |
协程的返回值类型
我们前面提到,区别一个函数是不是协程,是通过它的返回值类型来判断的。如果它的返回值类型满足协程的规则,那这个函数就会被编译成 协程。
那么,这个协程的规则 是什么呢?规则就是返回值类型能够实例化下面的模板类型 _Coroutine_traits:
cpp
template <class _Ret, class = void>
struct _Coroutine_traits {};
template <class _Ret>
struct _Coroutine_traits<_Ret, void_t<typename _Ret::promise_type>> {
using promise_type = typename _Ret::promise_type;
};
template <class _Ret, class...>
struct coroutine_traits : _Coroutine_traits<_Ret> {};
简单来说,就是返回值类型 _Ret 能够找到一个类型 _Ret::promise_type 与之相匹配。这个 promise_type 既可以是直接定义在 _Ret 当中的类型,也可以通过 using 指向已经存在的其他外部类型。
此时,我们就可以给出 Result 的部分实现了:
c++
struct Result
{
struct promise_type
{
...
};
};
//或者是
struct MyPromise
{
...
};
struct Result
{
using promise_type = MyPromise;
};
协程返回值对象的构建
看下面协程的示例:
cpp
Result Coroutine(int start_value) {
std::cout << start_value << std::endl;
co_await std::suspend_always{};
std::cout << start_value + 1 << std::endl;
};
这时你已经了解 C++ 当中如何界定一个协程。不过你可能会产生一个新的问题,返回值是从哪儿来的?协程体当中并没有给出 Result 对象创建的代码。
实际上,Result 对象的创建是由 promise_type 负责的,我们需要定义一个 get_return_object 函数来处理对 Result 对象的创建:
cpp
struct Result {
struct promise_type {
Result get_return_object() {
// 创建 Result 对象
return {};
}
...
};
};
不同于一般的函数,协程的返回值并不是在返回之前才创建,而是在协程的状态创建出来之后马上就创建的。也就是说,协程的状态被创建出来之后,会立即构造 promise_type 类型的对象promise(一个协程有且只有一个promise_type对象),进而调用 promise.get_return_object 来创建返回值对象。
promise_type 类型的构造函数参数列表如果与协程的参数列表一致,那么构造 promise_type 时就会调用这个构造函数。否则,就通过默认无参构造函数来构造 promise_type。
协程句柄coroutine_handle
coroutine_handle是协程中用于直接操控协程生命周期的句柄。
| 操作 | 说明 | 注意事项 |
|---|---|---|
resume() |
恢复挂起的协程执行。 | 只能在协程处于挂起状态时调用,对已完成的协程调用会导致未定义行为。 |
destroy() |
立即销毁协程帧,释放资源。 | 用于强制终止协程或清理在最终挂起后选择不自动销毁的协程。 |
done() |
检查协程是否已执行完成。 | 返回 bool 值,表示协程是否到达结束状态(通过 co_return 或异常)。 |
address() / from_address() |
进行句柄与原始指针之间的转换。 | 用于与 C 风格接口交互或自定义内存管理,需谨慎使用。 |
🔧 具体类型与获取方式
std::coroutine_handle<> 是一个泛型类模板的特化,具体使用时主要涉及两种类型:
- 无类型句柄 :
std::coroutine_handle<>
主要用于不知道或不关心具体promise_type的场景,侧重于基本的恢复(resume)和销毁(destroy)操作。 - 有类型句柄 :
std::coroutine_handle<PromiseType>
继承自std::coroutine_handle<>,增加了对特定promise_type的访问能力。可以通过promise()成员函数获取到其关联的promise对象的引用,从而读取或修改其中存储的数据(比如通过co_yield产生的值)。
获取协程句柄的常见方法 是在 promise_type 的 get_return_object() 成员函数中,使用 std::coroutine_handle<PromiseType>::from_promise(*this) 来通过 promise 对象创建对应的有类型句柄。
struct Result {
struct promise_type {
Result get_return_object() {
// 通过 promise 对象获取对应的协程句柄
return Result{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
// ... promise_type 的其他必要成员函数 ...
};
std::coroutine_handle<promise_type> handle_;
// ... MyTask 的其他成员 ...
};
执行体的执行
在协程的返回值被创建之后,协程体就要被执行了。
1、initial_suspend:
为了方便灵活扩展,协程体执行的第一步是调用 co_await promise.initial_suspend()(由编译器自动插入并默认执行的,无需显式编写 ),initial_suspend 的返回值就是一个等待对象(awaiter),如果返回值满足挂起的条件,则协程体在最一开始就立即挂起。这个点实际上非常重要,我们可以通过控制 initial_suspend 返回的等待体来实现协程的执行调度。有关调度的内容我们后面会专门探讨。
2、协程体的执行:
默认执行initial_suspend函数之后,就是协程体的执行了,协程体当中会存在co_await、co_yield、co_return三种协程特有的调用,其中:
| 关键字 | 主要作用 | 执行后协程状态 | 典型应用场景 |
|---|---|---|---|
co_await |
暂停执行,等待一个异步操作完成 | 挂起,等待被恢复 | 异步I/O、网络请求、定时操作 |
co_yield |
产生一个值并暂停执行,将值返回给调用者 | 挂起,等待被恢复 | 生成器(Generator)、惰性求值序列 |
co_return |
返回最终结果(或无返回值)并终止协程 | 执行完毕,进入完成状态 | 结束协程,输出最终成果 |
co_await前面已经介绍过了。
co_yield:生成器模式的利器:
co_yield value 可以理解为 co_await promise.yield_value(value) 的语法糖。它的主要行为是:
-
暂停协程:将值value 传递给协程外部的调用者(通常通过promise_type的特定成员函数保存这个值),然后立即挂起协程。
-
保持状态:协程挂起时,其所有局部状态都被保留。当调用者(例如,通过一个生成器对象的 next()方法)恢复协程时,执行将从co_yield语句之后紧接着的代码继续。
-
例如
cppstruct MyPromise { int current_value; // 用于存储co_yield产生的值 MyCoroutine get_return_object(); std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { std::terminate(); } // 处理co_yield std::suspend_always yield_value(int value) { current_value = value; return {}; // 每次yield后挂起 } void return_void() {} };
这种"产生一个值 -> 挂起 -> 恢复后继续执行"的循环,非常适合于实现按需生成数据的生成器。
co_return:协程的终结者:
co_return用于结束协程的执行,它可以带一个返回值,也可以不带。
- co_return expr; 用于有返回值的协程,表达式expr的值会通过promise_type的**return_value()**方法进行传递和存储。
- co_return; 用于无返回值的协程,这会调用promise_type的**return_void()**方法。在co_return执行之后,协程会开始执行清理工作,并最终进入完成状态,不再可恢复。
例如:
c++
struct Result {
struct promise_type {
void return_value(int value) {
...
}
...
};
};
Result Coroutine() {
...
co_return 1000;
};
//这里我们协程函数使用co_return 1000来返回一个整数,1000会作为参数传入return_value函数,即promise.return_value(1000)。
//这个值是可以存到promise_type对象当中,外部的调用者可以获取到。
3、final_suspend:
当协程执行完成或者抛出异常之后会先清理局部变量,接着调用 final_suspend 来方便开发者自行处理其他资源的销毁逻辑。final_suspend 也可以返回一个等待体使得当前协程挂起,但之后当前协程应当通过 coroutine_handle 的 destroy 函数来直接销毁,而不是 resume,因为一个已经执行完毕的协程是不可恢复的,强行恢复会导致未定义行为。
例如:
struct Result
{
struct primise_type
{
//...
//final_suspend返回suspend_always,表示协程完成后再挂起
std::suspend_always final_suspend() noexcept
{
return {};
}
};
};
当协程 co_return 后,会发生:
- 协程局部变量被析构。
- 调用
final_suspend(),它返回suspend_always,协程挂起。 - 协程状态(包含 promise 对象)在堆上保持存在,没有被销毁。
此时,你的代码有责任去销毁它:
cpp
// 假设你有办法获得已完成协程的句柄 coroutine_handle<> h
h.destroy(); // 手动销毁协程状态,释放内存
// 注意:此后句柄 h 失效,不可再使用
理解 final_suspend 的关键在于:
std::suspend_never(默认行为):协程完成后立即自动销毁,无需手动干预。适用于大多数简单场景。std::suspend_always(需手动管理) :协程完成后挂起,必须 手动调用.destroy()。这通常用于需要从协程外部检查结果或实现更复杂生命周期管理的场景,但需要格外小心,避免资源泄漏。
协程体抛出异常
协程体除了正常返回以外,也可以抛出异常。异常实际上也是一种结果的类型,因此处理方式也与返回结果相似。我们只需要在 promise_type 当中定义一个函数unhandled_exception,在异常抛出时这个函数就会被调用到。
| 处理环节 | 关键对象/方法 | 核心作用 |
|---|---|---|
| 异常捕获 | 协程框架 / promise_type::unhandled_exception() |
捕获协程体内未处理的异常,并通常将其保存至 promise_type 的成员(如 std::exception_ptr)中。 |
| 异常存储 | std::exception_ptr (于 promise_type 内) |
封装并保存异常对象,以便在协程恢复时再次抛出。 |
| 异常传播 | co_await 表达式的 await_resume() 方法 |
检查关联的 promise_type,若存在存储的异常,则在此处重新抛出。 |
cpp
struct Result {
struct promise_type {
std::exception_ptr exception; // 用于存储异常的成员变量
void unhandled_exception() {
exception = std::current_exception(); // 捕获并保存异常
}
// ... promise_type 的其他必要成员函数 ...
};
};
struct MyAwaiter {
// ... await_ready, await_suspend ...
void await_resume() {
if (promise.exception) {
std::rethrow_exception(promise.exception); // 重新抛出异常
}
}
};
实现一个序列生成器
实现目标:序列生成器通常的实现就是在一个协程内部通过某种方式向外传一个值出去,并且将自己挂起,外部调用者则可以获取到这个值,并且在后续继续恢复执行序列生成器来获取下一个值。
显然,挂起和向外部传值 的任务就需要通过co_await 来完成了,外部获取值 的任务就要通过协程的返回值来完成。
大概的结构是这样的:
cpp
Generator sequence()
{
int i = 0;
while(true)
{
co_await i++;
}
}
int main()
{
auto generator = sequence();
for(int i = 0; i < 10; i++)
{
std::cout << generator.next() << std::endl;
}
}
调用者获取值
所以现在的问题就是,generator.next()调用时恢复协程执行,并将下一个值传出来。
如果我们想要resume协程的话,就要想起前面的coroutine_handle,那么如何拿到coroutine_handle句柄呢?promise_type 是连接协程内外的桥梁,想要拿到什么,找 promise_type 要。标准库提供了一个通过 promise_type 的对象的地址获取 coroutine_handle 的函数,它实际上是 coroutine_handle 的一个静态函数:
cpp
template <class _Promise>
struct coroutine_handle {
static coroutine_handle from_promise(_Promise& _Prom) noexcept {
...
}
...
}
现在让我们把Generator类型进行完善:
cpp
struct Generator
{
struct promise_type
{
int value = 0;
//开始执行时不挂起,执行到第一个挂起点
std::suspend_never initial_suspend()
{
return {};
}
//执行结束不挂起
std::suspend_never final_suspend() noexcept
{
return {};
}
void unhandled_exception()
{}
Generator get_return_object()
{
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
void return_void()
{}
};
std::coroutine_handle<promise_type> _coro;
Generator(std::coroutine_handle<promise_type> coro)
: _coro(coro)
{}
int next()
{
_coro.resume();
//通过handle获取promise,然后再获取value
return _coro.promise().value;
}
};
协程内部挂起并传值
捋一下传值的过程:
协程内部 -> co_await(传入) -> promise_type -> next -> 输出
cpp
Generator sequence() {
int i = 0;
while (true) {
co_await i++;
}
}
根据前面对co_await的介绍,我们可以有两种思路:
1、给promise_type实现一个await_transform(int)函数
2、给整型实现一个operator co_await(int)运算符重载
以上两种二选一,但是对于int来说,C++中无法给基本类型定义运算符重载,所以在这里方案2行不通。
方案1:await_transform
定义了 await_transform 函数之后,co_await expr 就相当于 co_await promise.await_transform(expr) 了。
cpp
struct Generator
{
struct promise_type
{
int _value = 0;
//传值并挂起
std::suspend_always await_transform(int val)
{
this->_value = val;
return{};
}
...
};
...
};
方案2:实现operator co_await
这个方案就是给 int 定义 operator co_await 的重载,当然在这里是行不通的,因为在 C++ 当中我们是无法给基本类型定义运算符重载的:
cpp
auto operator co_await(int value)
{
struct IntAwaiter
{
int value;
bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<Generator::promise_type> handle) const
{
handle.promise().value = value;
}
void await_resume() { }
};
return IntAwaiter{.value = value};
}
完整代码:
cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
using namespace std;
struct Generator
{
struct promise_type
{
int _value = 0;
//传值并挂起
std::suspend_always await_transform(int val)
{
this->_value = val;
return{};
}
//开始执行时不挂起,执行到第一个挂起点
std::suspend_never initial_suspend()
{
return {};
}
//执行结束不挂起
std::suspend_never final_suspend() noexcept
{
return {};
}
void unhandled_exception()
{}
Generator get_return_object()
{
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
void return_void()
{}
};
std::coroutine_handle<promise_type> _coro;
Generator(std::coroutine_handle<promise_type> coro)
: _coro(coro)
{}
int next()
{
//要注意顺序,先获取值,再恢复协程,输出:0 1 2 3 4
int val = _coro.promise()._value;
_coro.resume();
return val;
// 也可以这样写,直接恢复协程,输出:1 2 3 4 5
// _coro.resume();
// return _coro.promise()._value;
}
};
Generator sequence()
{
int i = 0;
while(true)
{
co_await i++;
}
}
int main()
{
auto generator = sequence();
for(int i = 0; i < 5; i++)
{
std::cout << generator.next() << std::endl;
}
}
协程的销毁
我们现在的协程是能运行,同时也能按要求输出,但是存在缺陷。
问题1:无法确定是否存在下一个元素
当外部调用者或者恢复者试图调用next来获取下一个元素的时候,它其实并不知道能不能真的得到一个结果,程序也可能抛出异常,例如做如下更改:
cpp
Generator sequence() {
int i = 0;
// 只传出 5 个值
while (i < 5) {
co_await i++;
}
}
int main() {
auto gen = sequence();
for (int i = 0; i < 15; ++i) {
// 试图读取 15 个值
std::cout << gen.next() << std::endl;
}
return 0;
}
结果如下:
在执行第六次gen.next读取时,就报错了,原因是当协程体执行完之后,协程的状态就会被销毁(因为上面代码final_suspend定义为结束时不挂起),如果我们再访问协程的话,就相当于访问了一个野指针。
为了解决这个问题,我们需要增加一个has_next函数,用来判断是否还有新的值传出来,has_next函数调用的时候有两种情况:
1、已经有一个值传出来了,还没有被外部消费
2、还没有现成的值可以用,需要尝试恢复协程来看看还有没有下一个值传出来
可以通过定义一个标识,来判断value是否有效:
cpp
struct Generator {
// 协程执行完成之后,外部读取值时抛出的异常
class ExhaustedException: std::exception { };
struct promise_type {
int value;
bool is_ready = false;
...
}
...
}
这样外部使用时就需要先通过 has_next 来判断是否有下一个值,然后再去读取了:
cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
using namespace std;
struct Generator
{
class ExhaustedException : std::exception
{
};
struct promise_type
{
int _value;
bool _is_ready = false;
// 传值并挂起
std::suspend_always await_transform(int val)
{
this->_value = val;
this->_is_ready = true;
return {};
}
// 开始执行时不挂起,执行到第一个挂起点
std::suspend_never initial_suspend()
{
return {};
}
// 执行结束不挂起
std::suspend_never final_suspend() noexcept
{
return {};
}
void unhandled_exception()
{
}
Generator get_return_object()
{
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
void return_void()
{
}
};
std::coroutine_handle<promise_type> _coro;
Generator(std::coroutine_handle<promise_type> coro)
: _coro(coro)
{}
bool has_next()
{
if (!_coro || _coro.done())
{
return false;
}
// 如果没准备好就恢复执行
if (!_coro.promise()._is_ready)
{
cout << "resume" << endl;
_coro.resume();
}
if (_coro.done())
{
return false;
}
else
{
return true;
}
}
int next()
{
if (has_next())
{
// 要注意顺序,先获取值,再恢复协程,输出:0 1 2 3 4
int val = _coro.promise()._value;
_coro.promise()._is_ready = false;
return val;
}
throw ExhaustedException();
}
};
Generator sequence()
{
int i = 0;
while (i < 5)
{
co_await i++;
}
}
int main()
{
auto generator = sequence();
for(int i = 0; i < 15; i++)
{
if(generator.has_next())
{
std::cout << generator.next() << std::endl;
}
else
{
break;
}
}
return 0;
}
执行结果:
但是执行起来还是报错,原因是在协程退出之后又试图resume恢复协程,所以出错了。那为什么has_next的第一个判断语句if(!handle || handle.done())没有直接return false呢?原因是协程退出之后,协程状态销毁了,但是Generator对象还没销毁。
问题2:协程状态的销毁比 Generator 对象的销毁更早
我们前面提到过,协程的状态在协程体执行完之后就会销毁,除非协程挂起在 final_suspend 调用时。我们的例子当中 final_suspend 返回了 std::suspend_never,因此协程的销毁时机其实比 Generator 更早:
cpp
auto generator = sequence();
for (int i = 0; i < 15; ++i) {
if (generator.has_next()) {
std::cout << generator.next() << std::endl;
} else {
// 协程已经执行完,协程的状态已经销毁
break;
}
}
// generator 对象在此仍然有效
但我们上面的代码是存在问题的,因为我们在 has_next 当中调用了 coroutine_handle::done 来判断协程体是否执行完成,判断之前很可能协程已经销毁,coroutine_handle 这时候都已经是无效的了。
cpp
bool has_next() {
// 如果协程已经执行完成,理论上协程的状态已经销毁,handle 指向的是一个无效的协程
// 如果 handle 本身已经无效,因此 done 函数的调用此时也是无效的
if (!handle || handle.done()) {
return false;
}
...
}
因此为了让协程的状态的生成周期与 Generator 一致,我们必须将协程的销毁交给 Generator 来处理:
cpp
struct Generator {
class ExhaustedException: std::exception { };
struct promise_type {
...
// 总是挂起,让 Generator 来销毁
std::suspend_always final_suspend() noexcept { return {}; }
...
};
...
~Generator() {
// 销毁协程
handle.destroy();
}
};
总结来说就是,只要协程完全执行完(没挂起,或者挂起之后执行),协程的coroutine_handle句柄也随之无效,.done()在这个时候是无效的。但是协程函数返回值对象的生命周期随协程函数之外,所以返回值对象的生命周期久于或者等于协程的coroutine_handle;coroutine_handle的生命周期可以通过final_await进行挂起,在合适的时候再完全销毁。
问题3:复制对象导致协程被销毁
这个问题确切地说是问题 2 的解决方案不完善引起的。
我们在 Generator 的析构函数当中销毁协程,这本身没有什么问题,但如果我们把 Generator 对象做一下复制,例如从一个函数当中返回,情况可能就会变得复杂。例如:
c++
Generator sequence()
{
int i = 0;
while (i < 5)
{
co_await i++;
}
co_return;
}
Generator returns_generator()
{
{
Generator g = sequence();
if (g.has_next())
{
std::cout << g.next() << std::endl;
}
std::cout << &g << std::endl;
return g;
}
}
int main()
{
Generator generator = returns_generator();
std::cout << &generator << std::endl;
for (int i = 0; i < 15; i++)
{
if (generator.has_next())
{
std::cout << generator.next() << std::endl;
}
else
{
break;
}
}
return 0;
}
这段代码乍一看似乎没什么问题,但由于我们把 g 当做返回值返回了,这时候 g 这个对象就发生了一次复制,然后临时对象被销毁。接下来的事儿大家就很容易想到了,运行结果如下:

为了解决这个问题,需要处理Generator的复制构造器,我们只提供了右值复制构造器,对于左值复制构造器,我们直接删除掉以禁止使用。原因也很简单,对于每一个协程实例,都有且仅能有一个 Generator 实例与之对应,因此我们只支持移动对象,而不支持复制对象。:
cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
using namespace std;
struct Generator
{
class ExhaustedException : std::exception
{
};
struct promise_type
{
int _value;
bool _is_ready = false;
// 传值并挂起
std::suspend_always await_transform(int val)
{
this->_value = val;
this->_is_ready = true;
return {};
}
// 开始执行时不挂起,执行到第一个挂起点
std::suspend_never initial_suspend()
{
return {};
}
// 执行结束不挂起
std::suspend_always final_suspend() noexcept
{
return {};
}
void unhandled_exception()
{
}
Generator get_return_object()
{
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
void return_void()
{
}
};
std::coroutine_handle<promise_type> _coro;
Generator(std::coroutine_handle<promise_type> coro)
: _coro(coro)
{}
//右值资源转移
Generator(Generator&& generator) noexcept
:_coro(std::exchange(generator._coro, {}))
{}
//删除拷贝
Generator(Generator &) = delete;
Generator &operator=(Generator &) = delete;
~Generator()
{
_coro.destroy();
}
bool has_next()
{
if (!_coro || _coro.done())
{
return false;
}
// 如果没准备好就恢复执行
if (!_coro.promise()._is_ready)
{
cout << "resume" << endl;
_coro.resume();
}
if (_coro.done())
{
return false;
}
else
{
return true;
}
}
int next()
{
if (has_next())
{
// 要注意顺序,先获取值,再恢复协程,输出:0 1 2 3 4
int val = _coro.promise()._value;
_coro.promise()._is_ready = false;
return val;
}
throw ExhaustedException();
}
};
Generator sequence()
{
int i = 0;
while (i < 5)
{
co_await i++;
}
co_return;
}
Generator returns_generator()
{
Generator g = sequence();
if (g.has_next())
{
std::cout << g.next() << std::endl;
}
std::cout << &g << std::endl;
return g;
}
int main()
{
Generator generator = returns_generator();
std::cout << &generator << std::endl;
for (int i = 0; i < 15; i++)
{
if (generator.has_next())
{
std::cout << generator.next() << std::endl;
}
else
{
break;
}
}
return 0;
}
使用co_yield
序列生成器这个需求的实现其实有个更好的选择,那就是使用 co_yield。co_yield 就是专门为向外传值来设计的,如果大家对其他语言的协程有了解,也一定见到过各种 yield 的实现。
C++ 当中的 co_yield expr 等价于 co_await promise.yield_value(expr),我们只需要将前面例子当中的 await_transform 函数替换成 yield_value 就可以使用 co_yield 来传值了:
cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
using namespace std;
struct Generator
{
class ExhaustedException : std::exception
{
};
struct promise_type
{
int _value;
bool _is_ready = false;
// 传值并挂起
// std::suspend_always await_transform(int val)
// {
// this->_value = val;
// this->_is_ready = true;
// return {};
// }
//yield_value代替await_transform
std::suspend_always yield_value(int val)
{
this->_value = val;
this->_is_ready = true;
return {};
}
// 开始执行时不挂起,执行到第一个挂起点
std::suspend_never initial_suspend()
{
return {};
}
// 执行结束不挂起
std::suspend_always final_suspend() noexcept
{
return {};
}
void unhandled_exception()
{
}
Generator get_return_object()
{
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
void return_void()
{
}
};
std::coroutine_handle<promise_type> _coro;
Generator(std::coroutine_handle<promise_type> coro)
: _coro(coro)
{}
Generator(Generator&& generator) noexcept
:_coro(std::exchange(generator._coro, {}))
{}
Generator(Generator &) = delete;
Generator &operator=(Generator &) = delete;
~Generator()
{
_coro.destroy();
}
bool has_next()
{
if (!_coro || _coro.done())
{
return false;
}
// 如果没准备好就恢复执行
if (!_coro.promise()._is_ready)
{
cout << "resume" << endl;
_coro.resume();
}
if (_coro.done())
{
return false;
}
else
{
return true;
}
}
int next()
{
if (has_next())
{
// 要注意顺序,先获取值,再恢复协程,输出:0 1 2 3 4
int val = _coro.promise()._value;
_coro.promise()._is_ready = false;
return val;
}
throw ExhaustedException();
}
};
Generator sequence()
{
int i = 0;
while (i < 5)
{
//co_await i++;
co_yield i++;
}
co_return;
}
int main()
{
Generator generator = sequence();
for (int i = 0; i < 15; i++)
{
if (generator.has_next())
{
std::cout << generator.next() << std::endl;
}
else
{
break;
}
}
return 0;
}
尽管可以实现相同的效果,但通常情况下我们使用 co_await 更多的关注点在挂起自己,等待别人上,而使用 co_yield 则是挂起自己传值出去。因此我们应该针对合适的场景做出合适的选择。
使用序列生成器生成斐波那契数列
cpp
Generator fibonacci()
{
co_yield 0;
co_yield 1;
int a = 0, b = 1;
while(true)
{
co_yield a + b;
b = a + b;
a = b - a;
}
}
void test_fib()
{
auto g = fibonacci();
for (int i = 0; i < 10; i++)
{
if (g.has_next())
{
std::cout << g.next() << std::endl;
}
else
{
break;
}
}
}
序列生成器的泛化和函数时变换
添加模板
我们已经有了一个 int 版本的 Generator,实际上我们也很容易把它泛化成模板类型,改动的地方不多,基本上把原 Generator 类型当中的 int 替换成模板参数 T 即可,如下:
cpp
template<typename T>
struct Generator {
class ExhaustedException : std::exception {};
struct promise_type {
T value;
...
std::suspend_always yield_value(T value) {
...
}
...
};
...
T next() {
...
}
...
};
原来的斐波那契数列函数也需要稍作调整:
cpp
Generator<int> fibonacci() {
...
}
创建Generator的便捷函数
现在我们知道,想要创建 Generator 就需要定义一个函数或者 Lambda。不过从输出的结果上看, Generator 实际上就是一个"懒"序列,因此我们当然可以通过一个数组就能创建出 Generator 了。
使用数组创建 Generator 的版本实现比较简单,直接定义一个协程函数from_array:
cpp
template<class T>
struct Generator
{
...
// 这是个协程
Generator static from_array(T array[], int n)
{
for(int i = 0; i < n; i++)
{
co_yield array[i];
}
co_return;
}
...
};
//使用
void test2()
{
int array[] = {2, 2, 3, 4, 5};
auto g = Generator<int>::from_array(array, 5);
for (int i = 0; i < 5; i++)
{
if (g.has_next())
{
std::cout << g.next() << std::endl;
}
else
{
break;
}
}
}
因为使用数组传参之后数组会降为指针,所以需要传递元素数量n;如果不想传数量,可以把数组换成std::list:
cpp
template<class T>
struct Generator
{
...
Generator static from_list(std::list<T> list)
{
for (auto t: list)
{
co_yield t;
}
co_return;
}
...
};
auto generator = Generator<int>::from_list(std::list{1, 2, 3, 4});
每次传入都需要传入一个list,还可以改为初始化列表的版本:
cpp
template<class T>
struct Generator
{
...
Generator static from(std::initializer_list<T> args)
{
for (auto t: args)
{
co_yield t;
}
co_return;
}
...
};
auto generator = Generator<int>::from({1, 2, 3, 4});
不过,如果这对花括号也不用写的话,那就完美了。想要做到这一点,我们需要用到 C++ 17 的折叠表达式(fold expression)的特性,实现如下:
template<class T>
struct Generator
{
...
// 这是个协程
template<typename ...TArgs>
Generator static from(TArgs... args)
{
(co_yield args, ...);
co_return;
}
...
};
auto generator = Generator<int>::from(1, 2, 3, 4);
实现map和flat_map
熟悉函数式编程的读者可能已经意识到了,我们定义的 Generator 实际上已经非常接近 Monad 的定义了。那我们是不是可以给它实现 map 和 flat_map 呢?
实现map
map 就是将 Generator 当中的 T 映射成一个新的类型 U,得到一个新的 Generator<U>。下面我们给出第一个版本的 map 实现:
cpp
template<typename T>
struct Generator {
...
template<typename U>
Generator<U> map(std::function<U(T)> f) {
// 将 this 的所有权移动到新创建的 Generator 内部,确保生命周期的一致性
auto up_stream = std::move(*this);
// 判断 this 当中是否有下一个元素
while (up_stream.has_next()) {
// 使用 next 读取下一个元素
// 通过 f 将其变换成 U 类型的值,再使用 co_yield 传出
co_yield f(up_stream.next());
}
}
...
};
参数 std::function<U(T)> 当中的模板参数 U(T) 是个模板构造器,放到这里就表示这个函数的参数类型为 T,返回值类型为 U
cpp
// fibonacci 是上面定义的函数,返回 Generator<int>
Generator<std::string> generator_str = fibonacci().map<std::string>([](int i) {
return std::to_string(i);
});
通过 map 函数,我们将 Generator<int> 转换成了 Generator<std::string>,外部使用 generator_str 就会得到字符串。
当然,这个实现有个小小的缺陷,那就是 map 函数的模板参数 U 必须显式提供,如上例中的 <std::string>,这是因为我们在定义 map 时用到了模板构造器,这使得类型推断变得复杂。
为了解决这个问题,我们就要用到模板的一些高级特性了,下面给出第二个版本的 map 实现:
cpp
template<typename T>
struct Generator {
...
template<typename F>
Generator<std::invoke_result_t<F, T>> map(F f) {
auto up_steam = std::move(*this);
while (up_steam.has_next()) {
co_yield f(up_steam.next());
}
}
...
};
注意,这里我们直接用模板参数 F 来表示转换函数 f 的类型。map 本身的定义要求 F 的参数类型是 T,然后通过 std::invoke_result_t<F, T> 类获取 F 的返回值类型。这样我们在使用时就不需要显式的传入模板参数了:
cpp
Generator<std::string> generator_str = fibonacci().map([](int i) {
return std::to_string(i);
});
实现flat_map
在给出实现之前,我们需要先简单了解一下 flat_map 的概念。
前面提到的 map 是元素到元素的映射,而 flap_map 是元素到 Generator 的映射,然后将这些映射之后的 Generator 再展开(flat),组合成一个新的 Generator。这意味如果一个 Generator 会传出 5 个值,那么这 5 个值每一个值都会映射成一个新的 Generator,得到的这 5 个 Generator 又会整合成一个新的 Generator。
由此可知,map 不会使得新 Generator 的值的个数发生变化,flat_map 会。
下面我们给出 flat_map 的实现:
cpp
template<typename T>
struct Generator {
...
template<typename F>
// 返回值类型就是 F 的返回值类型
std::invoke_result_t<F, T> flat_map(F f) {
// 将 this 的所有权移动到新创建的 Generator 内部,确保生命周期的一致性
auto up_steam = std::move(*this);
while (up_steam.has_next()) {
// 值映射成新的 Generator
auto generator = f(up_steam.next());
// 将新的 Generator 展开
while (generator.has_next()) {
co_yield generator.next();
}
}
}
...
};
为了加深大家的理解,我们给出一个小例子:
cpp
Generator<int>::from(1, 2, 3, 4)
// 返回值类型必须显式写出来,表明这个函数是个协程
.flat_map([](auto i) -> Generator<int> {
for (int j = 0; j < i; ++j) {
// 在协程当中,我们可以使用 co_yield 传值出来
co_yield j;
}
})
.for_each([](auto i) {
if (i == 0) {
std::cout << std::endl;
}
std::cout << "* ";
});
这个例子的运行输出如下:
text
*
* *
* * *
* * * *
我们来稍微做下拆解。
Generator<int>::from(1, 2, 3, 4)得到的是序列1 2 3 4- flat_map 之后,得到
0 0 1 0 1 2 0 1 2 3
由于我们在 0 的位置做了换行,因此得到的输出就是 * 组成的三角形了。
其他有趣的函数
遍历所有值的 for_each
序列的最终使用,往往就是遍历:
cpp
template<typename T>
struct Generator {
...
template<typename F>
void for_each(F f) {
while (has_next()) {
f(next());
}
}
...
};
折叠值的 fold
Generator 会生成很多值,如果我们需要对这些值做一些整体的处理,并最终得到一个值,那么我们就需要折叠函数 fold:
cpp
template<typename T>
struct Generator {
...
template<typename R, typename F>
R fold(R initial, F f) {
R acc = initial;
while (has_next()) {
acc = f(acc, next());
}
return acc;
}
...
};
它需要一个初始值,函数 f 接收两个参数,分别是 acc 和序列生成器当前迭代的元素,每次经过 f 做运算得到的结果会作为下次迭代的 acc 传入,直到最后 acc 作为 fold 的返回值返回。
我们可以很方便地使用 fold 求和或者求取阶乘,例如:
cpp
// result: 720
auto result = Generator<int>::from(1, 2, 3, 4, 5, 6)
.fold(1, [](auto acc, auto i){
return acc * i; // 计算阶乘
});
求和函数 sum
求和本身可以用前面的 fold 来实现,当然我们也可以直接给出 sum 函数的定义:
cpp
template<typename T>
struct Generator {
...
T sum() {
T sum = 0;
while (has_next()) {
sum += next();
}
return sum;
}
...
};
用例:
cpp
// result: 21
auto result = Generator<double>::from(1.0, 2.0, 3.0, 4, 5, 6.0f).sum();
过滤部分值的 filter
你几乎可以在任何看到 map/flat_map 的场合看到 filter,毕竟有些值我们根本不需要。
想要实现这个过滤,只需要一个条件判断,下面我们给出 fitler 的实现:
cpp
template<typename T>
struct Generator {
...
template<typename F>
Generator filter(F f) {
auto up_steam = std::move(*this);
while (up_steam.has_next()) {
T value = up_steam.next();
if (f(value)) {
co_yield value;
}
}
}
...
};
截取前 n 个值的 take(n)
序列生成器往往与懒序列 同时出现,因为懒序列 之所以懒,往往是因为它的长度可能很长(甚至无限,例如斐波那契数列),一次性将所有的值加载出来会比较影响性能。
对于这种很长的懒序列,我们最终能用到的值可能并不多,因此我们需要一个函数 take(n) 对序列的前 n 个做截取。
它的实现也是显而易见的:
cpp
template<typename T>
struct Generator {
...
Generator take(int n) {
auto up_steam = std::move(*this);
int i = 0;
while (i++ < n && up_steam.has_next()) {
co_yield up_steam.next();
}
}
...
};
截取到指定条件的 take_while
take_while 的实现就好像是 filter 与 take(n) 的一个结合:
cpp
template<typename T>
struct Generator {
...
template<typename F>
Generator take_while(F f) {
auto up_steam = std::move(*this);
while (up_steam.has_next()) {
T value = up_steam.next();
if (f(value)) {
co_yield value;
} else {
break;
}
}
}
...
};
例如我们想要截取小于 100 的所有斐波那契数列:
cpp
fibonacci().take_while([](auto i){
return i < 100;
}).for_each([](auto i){
std::cout << i << " ";
});
就会得到:
text
0 1 1 2 3 5 8 13 21 34 55 89
函数的调用时机
前面给出了这么多函数的实现,目的主要是为了让大家充分理解 C++ 协程的妙处。为了进一步确认大家对于前面例子的理解程度,我们再给出一个例子,请大家思考这当中的每一个 lambda 分别调用几次,以及输出什么:
cpp
Generator<int>::from(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.filter([](auto i) {
std::cout << "filter: " << i << std::endl;
return i % 2 == 0;
})
.map([](auto i) {
std::cout << "map: " << i << std::endl;
return i * 3;
})
.flat_map([](auto i) -> Generator<int> {
std::cout << "flat_map: " << i << std::endl;
for (int j = 0; j < i; ++j) {
co_yield j;
}
}).take(3)
.for_each([](auto i) {
std::cout << "for_each: " << i << std::endl;
});
大家在分析的时候,请牢记 Generator 生成的序列是懒序列,只要最终访问到的时候才会生成。
这意味着中间的 map 其中根本不会主动消费 Generator,flat_map 也不会,filter 也不会,take 也不会。只有 for_each 调用的时候,才会真正需要知道 Generator 当中都有什么。
输出的结果如下:
text
filter: 1
filter: 2
map: 2
flat_map: 6
for_each: 0
for_each: 1
for_each: 2
Generator 的所有权
在前面的函数实现中,有部分函数会在一开始转移 this 的所有权:
cpp
auto up_steam = std::move(*this);
这是为什么呢?
因为 this 很有可能是一个泛左值,在函数返回之后会立即销毁。例如:
cpp
Generator<int> generator = Generator<int>::from(1, 2, 3, 4).map([](int i) {
return i * 2;
});
generator.for_each([](auto i) {
std::cout << i << std::endl;
});
map 函数的 this 实际上是 from 函数的返回值,这个返回值会在 map 函数返回之后立即进行析构。如果我们不在 map 内部转移 this 的所有权,那么 this 对应的协程也会随着 from 的返回值的析构而销毁。
还有一个小问题,为什么有些函数不需要转移 this 的所有权呢?
这些函数包括 sum、for_each、fold 等等,在这些函数内部 this 的值就会被消费完毕,因此 Generator 是否销毁对后续的程序执行没有任何影响。事实上,细心的读者可能已经发现,这些函数还有一个非常显著的特征:它们的返回值都不是 Generator。
通用异步任务Task
协程主要用来降低异步任务的编写复杂度,异步任务各式各样,但归根到底就是一个结果的获取。
为了方便介绍后续的内容,我们需要再定义一个类型 Task 来作为协程的返回值。Task 类型可以用来封装任何返回结果的异步行为(持续返回值的情况可能更适合使用序列生成器)。
cpp
Task<int> simple_task2()
{
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
co_return 2;
}
Task<int> simple_task3()
{
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
co_return 3;
}
Task<int> simple_task()
{
auto result2 = co_await simple_task2();
auto result3 = co_await simple_task3();
co_return 1 + result2 + result3;
}
我们定义以 Task<ResultType> 为返回值类型的协程,并且可以在协程内部使用 co_await 来等待其他 Task 的执行。
外部非协程内的函数当中访问 Task 的结果时,我们可以通过回调或者同步阻塞调用两种方式来实现:
cpp
Task<int> simple_task2()
{
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
co_return 2;
}
Task<int> simple_task3()
{
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
co_return 3;
}
Task<int> simple_task()
{
auto result2 = co_await simple_task2();
auto result3 = co_await simple_task3();
co_return 1 + result2 + result3;
}
int main()
{
auto simpleTask = simple_task();
//异步方式
simpleTask.then([](int i)
{
//... //i = 6
}).catching([](std::exception &e)
{
//...
});
//同步方式
try
{
auto i = simpleTask.get_result();
//... //i = 6
}
catch (std::exception &e)
{
//...
}
return 0;
}
按照这个效果,我们大致可以分析得到:
- 需要一个结果类型来承载正常返回和异常抛出的情况。
- 需要为
Task定义相应的promise_type类型来支持co_return和co_await。 - 为
Task实现获取结果的阻塞函数get_result或者用于获取返回值的回调then以及用于获取抛出的异常的回调catching。
结果类型的定义
描述Task正常返回的结果和抛出的异常,只需要定义一个持有二者的类型既可:
cpp
template<class T>
struct Result
{
//初始化为默认值
explicit Result() = default;
//当Task正常返回时用结果初始化Result
explicit Result(T &&value) : _value(std::move(value))
{}
//当Task抛出异常时用异常初始化Result
explicit Result(std::exception_ptr &&exception_ptr) : _exception_ptr(std::move(exception_ptr))
{}
//读取结果,有异常则抛出异常
T get_or_throw()
{
if(_exception_ptr)
{
std::rethrow_exception(_exception_ptr);
}
return _value;
}
private:
T _value{};
std::exception_ptr _exception_ptr;
};
其中,Result 的模板参数 T 对应于 Task 的返回值类型。有了这个结果类型,我们就可以很方便地在需要读取结果的时候调用get_or_throw。
promise_type的定义
promise_type的定义自然是最为重要的部分。
cpp
//promise_type
template<class ResultType>
struct TaskPromise
{
//协程立即执行
std::suspend_never initial_suspend()
{
return {};
}
//执行结束后挂起,等待外部销毁。该逻辑与前面的Generator类型
std::suspend_always final_suspend() noexcept //noexcept表示不会抛出异常
{
return {};
}
//构造协程的返回值对象Task
Task<ResultType> get_return_object()
{
return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
//当协程抛出异常时,将异常存储到result中
void unhandled_exception()
{
_result = Result<ResultType>(std::current_exception());
}
//协程正常返回时,将结果存储到result中
void return_value(ResultType value)
{
_result = Result<ResultType>(std::move(value));
}
private:
//使用 std::optional 可以区分协程是否执行完成
std::optional<Result<ResultType>> _result;
//std::optional<T> 是一个模板类,
//定义在 <optional> 头文件中。你可以将它理解为一个类型安全的容器,
//这个容器最多只能容纳一个类型为 T 的对象。它的关键之处在于其状态是可变的:
//有值:容器内包含一个类型为T的有效对象
//无值:容器是空的,不包含任何有效对象
};
光有这些还不够,我们还需要为 Task 添加 co_await 的支持。这里我们有两个选择:
- 为
Task实现co_await运算符(运算符重载) - 在
promise_type当中定义await_transform
从效果上来看,二者都可以做到。但区别在于,await_transform 是 promsie_type 的内部函数,可以直接访问到 promise 内部的状态;同时,await_transform 的定义也会限制协程内部对于其他类型的 co_await 的支持,将协程内部的挂起行为更好的管控起来,方便后续我们做统一的线程调度。因此此处我们采用 await_transform 来为 Task 提供 co_await 支持:
cpp
template <class R>
struct TaskAwaiter
{
explicit TaskAwaiter(Task<R> &&task) noexcept
: _task(std::move(task))
{
}
TaskAwaiter(TaskAwaiter &&completion) noexcept
: _task(std::exchange(completion.task, {}))
{
}
TaskAwaiter(TaskAwaiter &) = delete;
TaskAwaiter &operator=(TaskAwaiter &) = delete;
constexpr bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
// 当 task 执行完之后调用 resume
_task.finally([handle]()
{ handle.resume();
});
}
// 协程恢复执行时,被等待的 Task 已经执行完,调用 get_result 来获取结果
R await_resume() noexcept
{
return _task.get_result();
}
private:
Task<R> _task;
};
//promise_type
template<class ResultType>
struct TaskPromise
{
...
//await_transform,注意模板
template<typename _ResultType>
TaskAwaiter<_ResultType> await_transform(Task<_ResultType> &&task)
{
return TaskAwaiter<_ResultType>(std::move(task));
}
...
};
代码很简单,返回了一个 TaskAwaiter 的对象。不过再次请大家注意,这里存在两个 Task,一个是 TaskPromise 对应的 Task,一个是 co_await 表达式的操作数 Task,后者是 await_transform 的参数。
当一个 Task 实例被 co_await 时,意味着它在 co_await 表达式返回之前已经执行完毕,当 co_await 表达式返回时,Task 的结果也就被取到,Task 实例在后续就没有意义了。因此 TaskAwaiter 的构造器当中接收 Task &&,防止 co_await 表达式之后继续对 Task 进行操作。
同步阻塞获取结果
为了防止 result 被外部随意访问,我们特意将其改为私有成员。接下来我们还需要提供相应的方式方便外部访问 result。
实现同步阻塞的结果返回:
cpp
// promise_type
template <class ResultType>
struct TaskPromise
{
// 协程立即执行
std::suspend_never initial_suspend()
{
return {};
}
// 执行结束后挂起,等待外部销毁
std::suspend_always final_suspend() noexcept // noexcept表示不会抛出异常
{
return {};
}
// 构造协程的返回值对象Task
Task<ResultType> get_return_object()
{
return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
// await_transform
template <typename _ResultType>
TaskAwaiter<_ResultType> await_transform(Task<_ResultType> &&task)
{
return TaskAwaiter<_ResultType>(std::move(task));
}
// 当协程抛出异常时,将异常存储到result中
void unhandled_exception()
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::current_exception());
// 通知get_result当中的wait
_completion.notify_all();
}
// 协程正常返回时,将结果存储到result中
void return_value(ResultType value)
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(value));
// 通知get_result当中的wait
_completion.notify_all();
}
ResultType get_result()
{
std::unique_lock lock(_completion_lock);
if(!_result.has_value())
{
_completion.wait(lock);
}
//如果有值,则直接返回(或者抛出异常)
return _result->get_or_throw();
}
private:
// 使用 std::optional 可以区分协程是否执行完成
std::optional<Result<ResultType>> _result;
// std::optional<T> 是一个模板类,
// 定义在 <optional> 头文件中。你可以将它理解为一个类型安全的容器,
// 这个容器最多只能容纳一个类型为 T 的对象。它的关键之处在于其状态是可变的:
// 有值:容器内包含一个类型为T的有效对象
// 无值:容器是空的,不包含任何有效对象
std::mutex _completion_lock;
std::condition_variable _completion;
};
既然要阻塞,就免不了用到锁(mutex)和条件变量(condition_variable),熟悉它们的读者一定觉得事情变得不那么简单了:这些工具在以往都是用在多线程并发的环境当中的。我们现在这么写其实也是为了后续应对多线程的场景,有关多线程调度的问题我们将在下一篇文章当中讨论。
异步结果回调
异步回调的实现稍微复杂一些,其实主要复杂在对于函数的运用。实际上对于回调的支持,主要就是支持回调的注册和回调的调用。根据结果类型的不同,回调又分为返回值的回调或者抛出异常的回调:
cpp
template <typename ResultType>
struct TaskPromise
{
...
void unhandled_exception()
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(std::current_exception()));
// 通知get_result当中的wait
_completion.notify_all();
// 调用回调
notify_callback();
}
void return_value(ResultType value)
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(value));
_completion.notify_all();
// 调用回调
notify_callback();
}
...
// 注册回调
void on_completed(std::function<void(Result<ResultType>)> &&func)
{
std::unique_lock lock(_completion_lock);
// 加锁判断result
if (_result.has_value())
{
//_result有值,则调用回调
auto value = _result.value();
// 解锁之后调用func
lock.unlock();
func(std::move(value));
}
else
{
// 否则添加回调函数,等待回调
_completion_callbacks.push_back(func);
}
}
private:
...
std::list<std::function<void(Result<ResultType>)>> _completion_callbacks;
// 通知回调,调用回调函数,将结果传递给回调函数
void notify_callback()
{
auto value = _result.value();
for (auto &callback : _completion_callbacks)
{
callback(value);
}
// 调用完成,清空回调
_completion_callbacks.clear();
}
};
同样地,如果只是在单线程环境内运行协程,这里的异步回调的作用可能并不明显。这里只是先给出定义,待我们后续支持线程调度之后,这些回调支持就会非常有价值了。
Task的实现
现在我们已经实现了最为关键的 promise_type,接下来给出 Task 类型的完整定义。我想各位读者一定明白,Task 不过就是个摆设,它的能力大多都是通过调用 promise_type 来实现的。
cpp
template <typename ResultType>
struct Task
{
using promise_type = TaskPromise<ResultType>;
ResultType get_result()
{
return _handle.promise().get_result();
}
// 如果有值,处理值
Task &then(std::function<void(ResultType)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
func(result.get_or_throw());
}
catch (std::exception &e)
{
// 忽略异常
} });
return *this;
}
// 如果有异常,处理异常
Task &catching(std::function<void(std::exception &)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
// 忽略返回值
result.get_or_throw();
}
catch (std::exception &e)
{
func(e);
} });
return *this;
}
Task &finally(std::function<void()> &&func)
{
_handle.promise().on_completed([func](auto result)
{ func(); });
return *this;
}
explicit Task(std::coroutine_handle<promise_type> handle) noexcept
: _handle(handle)
{
}
Task(Task &&task) noexcept
: _handle(std::exchange(task._handle, {}))
{
}
Task(Task &) = delete;
Task &operator=(Task &) = delete;
~Task()
{
if (_handle)
{
_handle.destroy();
}
}
private:
std::coroutine_handle<TaskPromise<ResultType>> _handle;
};
现在我们完成了 Task 的第一个通用版本的实现,这个版本的实现当中尽管我们对 Task 的结果做了加锁,但考虑到目前我们仍没有提供线程切换的能力,因此这实际上是一个无调度器版本的 Task 实现。
无调度器版本的Task完整代码及运行结果
cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
#include <optional>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <list>
#include <memory>
#include <chrono>
#include <thread>
#include <unistd.h>
using namespace std;
template <typename R>
struct TaskAwaiter;
template <typename ResultType>
struct TaskPromise;
template <typename ResultType>
struct Task;
template <typename T>
struct Result
{
// 初始化为默认值
explicit Result() = default;
// 当Task正常返回时用结果初始化Result
explicit Result(T &&value)
: _value(std::move(value))
{
}
// 当Task抛出异常时用异常初始化Result
explicit Result(std::exception_ptr &&exception_ptr)
: _exception_ptr(std::move(exception_ptr))
{
}
// 读取结果,有异常则抛出异常
T get_or_throw()
{
if (_exception_ptr)
{
// 重新抛出
std::rethrow_exception(_exception_ptr);
}
return _value;
}
private:
T _value;
std::exception_ptr _exception_ptr;
};
template <typename R>
struct TaskAwaiter
{
explicit TaskAwaiter(Task<R> &&task) noexcept
: _task(std::move(task))
{
}
TaskAwaiter(TaskAwaiter &&completion) noexcept
: _task(std::exchange(completion._task, {}))
{
}
TaskAwaiter(TaskAwaiter &) = delete;
TaskAwaiter &operator=(TaskAwaiter &) = delete;
// constexpr的作用:
// 1. 可以在编译时计算出结果
// 2. 可以在编译时判断是否需要挂起
constexpr bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
//_task执行完之后才调用resume,此时一定有值
_task.finally([handle]()
{ handle.resume(); });
}
// 这里是co_await之后的返回值
R await_resume() noexcept
{
return _task.get_result();
}
private:
Task<R> _task;
};
template <typename ResultType>
struct TaskPromise
{
std::suspend_never initial_suspend()
{
return {};
}
std::suspend_always final_suspend() noexcept
{
return {};
}
Task<ResultType> get_return_object()
{
return Task<ResultType>{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
template <typename _ResultType>
TaskAwaiter<_ResultType> await_transform(Task<_ResultType> &&task)
{
return TaskAwaiter<_ResultType>(std::move(task));
}
void unhandled_exception()
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(std::current_exception()));
// 通知get_result当中的wait
_completion.notify_all();
// 调用回调
notify_callback();
}
void return_value(ResultType value)
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(value));
_completion.notify_all();
// 调用回调
notify_callback();
}
// 获取结果,若没有结果则阻塞等待--提供给Task使用
ResultType get_result()
{
// 如果_result没有值,则等待写入值之后调用notify_all
std::unique_lock lock(_completion_lock);
if (!_result.has_value())
{
// 等待写入值之后调用notify_all
_completion.wait(lock);
}
// 有值,则返回(或者抛出异常)
return _result->get_or_throw();
}
// 注册回调
void on_completed(std::function<void(Result<ResultType>)> &&func)
{
std::unique_lock lock(_completion_lock);
// 加锁判断result
if (_result.has_value())
{
//_result有值,则调用回调
auto value = _result.value();
// 解锁之后调用func
lock.unlock();
func(std::move(value));
}
else
{
// 否则添加回调函数,等待回调
_completion_callbacks.push_back(func);
}
}
private:
std::optional<Result<ResultType>> _result;
std::mutex _completion_lock;
std::condition_variable _completion;
// 回调列表,我们允许对同一个 Task 添加多个回调
std::list<std::function<void(Result<ResultType>)>> _completion_callbacks;
// 通知回调,调用回调函数,将结果传递给回调函数
void notify_callback()
{
auto value = _result.value();
for (auto &callback : _completion_callbacks)
{
callback(value);
}
// 调用完成,清空回调
_completion_callbacks.clear();
}
};
template <typename ResultType>
struct Task
{
using promise_type = TaskPromise<ResultType>;
ResultType get_result()
{
return _handle.promise().get_result();
}
// 如果有值,处理值
Task &then(std::function<void(ResultType)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
func(result.get_or_throw());
}
catch (std::exception &e)
{
// 忽略异常
} });
return *this;
}
// 如果有异常,处理异常
Task &catching(std::function<void(std::exception &)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
// 忽略返回值
result.get_or_throw();
}
catch (std::exception &e)
{
func(e);
} });
return *this;
}
Task &finally(std::function<void()> &&func)
{
_handle.promise().on_completed([func](auto result)
{ func(); });
return *this;
}
explicit Task(std::coroutine_handle<promise_type> handle) noexcept
: _handle(handle)
{
}
Task(Task &&task) noexcept
: _handle(std::exchange(task._handle, {}))
{
}
Task(Task &) = delete;
Task &operator=(Task &) = delete;
~Task()
{
if (_handle)
{
_handle.destroy();
}
}
private:
std::coroutine_handle<TaskPromise<ResultType>> _handle;
};
// test
Task<int> simple_task2()
{
std::cout << "task 2 start ..." << std::endl;
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
std::cout << "task 2 return after 1s." << std::endl;
co_return 2;
}
Task<int> simple_task3()
{
std::cout << "task 3 start ..." << std::endl;
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
std::cout << "task 3 return after 2s." << std::endl;
co_return 3;
}
Task<int> simple_task()
{
std::cout << "task start ..." << std::endl;
auto result2 = co_await simple_task2();
std::cout << "return from task2: " << result2 << std::endl;
auto result3 = co_await simple_task3();
std::cout << "return from task3: " << result3 << std::endl;
co_return 1 + result2 + result3;
}
int main()
{
auto simpleTask = simple_task();
simpleTask.then([](int i)
{ std::cout << "simple task end: " << i << std::endl; })
.catching([](std::exception &e)
{ std::cout << "error occurred " << e.what() << std::endl; });
try
{
auto i = simpleTask.get_result();
std::cout << "simple task end from get: " << i << std::endl;
}
catch (const std::exception &e)
{
std::cout << "error: " << e.what() << std::endl;
}
return 0;
}
bash
ubuntu@EVA:~/myfile/test$ g++ -o test2 test2.cpp -std=c++20
ubuntu@EVA:~/myfile/test$ ./test3
task start ...
task 2 start ...
task 2 return after 1s.
await_ready
await_resume
return from task2: 2
task 3 start ...
task 3 return after 2s.
await_ready
await_resume
return from task3: 3
simple task end from get: 6
由于我们的任务在执行过程中没有进行任何线程切换,因此各个 Task 的执行实际上是串行的,就如同我们调用普通函数一样。当然,这显然不是我们的最终目的,下一篇我们就来介绍如何给 Task 增加调度器的支持。
协程调度器
调度器抽象设计
为了实现协程的异步调度,我们需要提供调度器的实现。调度器听起来有些厉害,但实际上就是负责执行一段逻辑的工具。
下面我们给出调度器的抽象设计:
cpp
//调度器抽象接口
class AbstractExecutor
{
public:
virtual void execute(std::function<void()> &&func) = 0;
};
调度的位置
现在我们已经知道了调度器的样子,那么问题来了,怎么才能把它接入到协程当中呢?这个问题换个说法,那就是什么情况下我们需要调度,或者说什么情况下我们可以实现调度。
这个问题如果你不知道答案,让你随便蒙,你大概也没有什么其他的选项可以选。因为协程的本质就是挂起和恢复,因此想要实现调度,就必须在挂起和恢复上做文章。想要在 C++ 的协程的挂起和恢复上做文章,那我们就只能考虑定制 Awaiter 了。
cpp
// 调度器的类型有多种,因此专门提供一个模板参数 Executor
template<typename Result, typename Executor>
struct TaskAwaiter
{
explicit TaskAwaiter(AbstractExecutor *executor, Task<Result, Executor> &&task) noexcept
: _executor(executor),_task(std::move(task))
{
}
TaskAwaiter(TaskAwaiter &&completion) noexcept
: _executor(completion._executor),_task(std::exchange(completion._task, {}))
{
}
...
// constexpr的作用:
// 1. 可以在编译时计算出结果
// 2. 可以在编译时判断是否需要挂起
constexpr bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
// //_task执行完之后才调用resume,此时一定有值
// _task.finally([handle]()
// { handle.resume(); });
//改为调度器调度
_task.finally([handle, this]()
{
_executor->execute([handle]()
{
handle.resume();
});
});
}
// 这里是co_await之后的返回值
Result await_resume() noexcept
{
return _task.get_result();
}
private:
Task<Result,Executor>_task;
//调度器
AbstractExecutor *_executor;
};
调度器的创建
TaskAwaiter 当中的调度器实例是从外部传来 的,这样设计的目的是希望把调度器的创建和绑定交给协程本身 。换句话说,调度器应该属于协程。这样设计的好处就是协程内部的代码均会被调度到它对应的调度器上执行,可以确保逻辑的一致性和正确性。
这么看来,调度器应该与 Task 或者 TaskPromise 绑定到一起。
当协程创建时,我们可以以某种方式执行一个调度器,让协程的执行从头到尾都调度到这个调度器上执行。例如:
cpp
Task<int, LooperExecutor> simple_task()
{
//协程启动时就要调度到LooperExecutor上
std::cout << "task start ..." << std::endl;
auto result2 = co_await simple_task2();
//协程从simple_task2挂起后恢复执行,也要调度到LooperExecutor上
std::cout << "return from task2: " << result2 << std::endl;
auto result3 = co_await simple_task3();
//同上
std::cout << "return from task3: " << result3 << std::endl;
co_return 1 + result2 + result3;
}
我们通过模板参数为 Task 绑定了一个叫做 LooperExecutor 的调度器(我们现在先不去管 LooperExecutor 的具体实现,这个我们后面会讲),这样一来,我们希望 simple_task 当中所有的代码都会被调度到 LooperExecutor 上执行。
请大家参考注释的说明,我们了解到所有挂起的位置都需要在恢复时拿到同一个 LooperExecutor 的实例,因此我们考虑首先对 TaskPromise 的定义做一下修改,引入 Executor:
cpp
template <typename ResultType, typename Executor>
struct TaskPromise
{
//协程启动时也需要在恢复时实现调度
DispatchAwaiter initial_suspend()
{
return DispatchAwaiter{&_executor};
}
std::suspend_always final_suspend() noexcept
{
return {};
}
Task<ResultType, Executor> get_return_object()
{
return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
template <typename _ResultType, typename _Executor>
TaskAwaiter<_ResultType, _Executor> await_transform(Task<_ResultType, _Executor> &&task)
{
return TaskAwaiter<_ResultType, _Executor>(&executor, std::move(task));
}
...
private:
Executor _executor;
...
};
由于我们在 TaskPromise 当中定义了 await_transform,因此协程当中只支持对 Task 类型的 co_await 操作,这样可以保证所有的 co_await <task> 都会在恢复执行时通过 TaskAwaiter 来确保后续逻辑的正确调度。
剩下的就是协程在启动时的 initial_suspend 了,这个也比较容易处理,我们给出 DispatchAwaiter 的定义,这样就协程内部的所有逻辑都可以顺利地调度到协程对应调度调度器上了:
cpp
struct DispatchAwaiter
{
explicit DispatchAwaiter(AbstractExecutor *executor) noexcept
: _executor(executor)
{
}
bool await_ready() const
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) const
{
//调度到协程对应的调度器上
_executor->execute([handle]()
{
handle.resume();
});
}
void await_resume() const
{}
private:
AbstractExecutor *_executor;
};
Task的改动不大,增加了模板参数Executor:
cpp
// NewThreadExecutor 是 AbstractExecutor 的子类,作为模板参数 Executor 的默认值
template<typename ResultType, typename Executor = NewThreadExecutor>
struct Task
{
using promise_type = TaskPromise<ResultType, Executor>;
...
};
我们还可以默认给 Task 指定一个调度器的实现NewThreadExecutor。这些调度器可以通过指定类型在 TaskPromise 当中执行初始化,因为我们会保证他们都会有默认的无参构造器实现。
调度器实现
NoopExecutor
无操作调度器,直接在当前线程执行
cpp
// 无操作调度器,直接在当前线程执行
class NoopExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
func();
}
};
NewThreadExecutor
每次调度都创建一个新的线程
cpp
//创建新线程调度器,每次调度都创建一个新的线程,detach表示线程不受主线程控制,主线程可以继续执行
class NewThreadExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
std::thread(func).detach();
}
};
AsyncExecutor
思路与NewThreadExecutor差不多,只是调度时交给了是std::async去执行,利用其背后的线程调度,提升线程的利用率
cpp
//异步调度器,每次调度都创建一个新的线程,async表示线程会在后台执行,主线程可以继续执行
//与NewThreadExecutor差不多,区别是调度时交给std::async处理,利用async背后的线程调度,提升线程的利用率
class AsyncExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
auto future = std::async(func);
}
};
LooperExecutor
简单的单线程事件循环
- 当队列为空时,Looper 的线程通过
wait来实现阻塞等待。 - 有新任务加入时,通过
notify_one来通知run_loop继续执行。
cpp
//简单的单事件循环调度器
class LooperExecutor : public AbstractExecutor
{
private:
std::condition_variable _queue_condition;
std::mutex _queue_lock;
std::queue<std::function<void()>> _executable_queue;
//true为活动状态,false为停止状态
std::atomic<bool> _is_active;
//工作线程,用于处理事件循环
std::thread _work_thread;
//处理事件循环
void run_loop()
{
//检查当前事件循环是否处于工作状态,或者调度器队列没有清空
while(_is_active.load(std::memory_order_relaxed) || !_executable_queue.empty())
{
std::unique_lock lock(_queue_lock);
//如果队列为空,就需要等待新任务加入队列或者关闭事件循环的通知
if(_executable_queue.empty())
{
//队列为空,那么说明收到的是关闭的通知
_queue_condition.wait(lock);
//如果队列为空,那么说明收到的是关闭的通知
if(_executable_queue.empty())
{
//现有逻辑下此处break可以
//使用continue可以再次检查状态和队列,方便将来扩展
continue;
}
}
//取出任务,解锁,执行任务
auto func = _executable_queue.front();
_executable_queue.pop();
lock.unlock();
func();
}
}
public:
LooperExecutor()
{
_is_active.store(true, std::memory_order_relaxed);
_work_thread = std::thread(&LooperExecutor::run_loop, this);
}
~LooperExecutor()
{
shutdown(false);
//等待线程执行完,防止出现意外情况
join();
}
void execute(std::function<void()> &&func) override
{
std::unique_lock lock(_queue_lock);
if(_is_active.load(std::memory_order_relaxed))
{
_executable_queue.push(func);
lock.unlock();
//通知队列,主要用于队列之前为空时调用wait等待的情况
//通知不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_one();
}
}
//关闭事件循环,wait_for_complete表示是否等待所有任务执行完毕
void shutdown(bool wait_for_complete = true)
{
//修改后立即生效,在run_loop当中就能尽早(加锁前)就检测到_is_active的变化
_is_active.store(false, std::memory_order_relaxed);
if(!wait_for_complete)
{
std::unique_lock lock(_queue_lock);
//清空任务队列
decltype(_executable_queue) empty_queue;
std::swap(_executable_queue, empty_queue);
lock.unlock();
}
//通知wait函数,避免Looper线程不退出
//不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_all();
}
void join()
{
if(_work_thread.joinable())
{
_work_thread.join();
}
}
};
SharedLooperExecutor
其实就是LooperExecutor的马甲,它的作用就是让各个协程共享一个LooperExecutor实例。
cpp
//共享事件循环调度器,使用单例模式,作用就是让各个协程共享一个LooperExecutor实例
//使用时直接调用execute方法即可
class SharedLooperExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
static LooperExecutor SharedLooperExecutor;
SharedLooperExecutor.execute(std::move(func));
}
};
完整代码和运行结果
cpp
//task.cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
#include <optional>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <list>
#include <memory>
#include <chrono>
#include <thread>
#include <unistd.h>
#include "executor.hpp"
using namespace std;
template<typename Result, typename Executor>
struct TaskAwaiter;
template <typename ResultType, typename Executor>
struct TaskPromise;
template<typename ResultType, typename Executor>
struct Task;
template <typename T>
struct Result
{
// 初始化为默认值
explicit Result() = default;
// 当Task正常返回时用结果初始化Result
explicit Result(T &&value)
: _value(std::move(value))
{
}
// 当Task抛出异常时用异常初始化Result
explicit Result(std::exception_ptr &&exception_ptr)
: _exception_ptr(std::move(exception_ptr))
{
}
// 读取结果,有异常则抛出异常
T get_or_throw()
{
if (_exception_ptr)
{
// 重新抛出
std::rethrow_exception(_exception_ptr);
}
return _value;
}
private:
T _value;
std::exception_ptr _exception_ptr;
};
// 调度器的类型有多种,因此专门提供一个模板参数 Executor
template<typename Result, typename Executor>
struct TaskAwaiter
{
explicit TaskAwaiter(AbstractExecutor *executor, Task<Result, Executor> &&task) noexcept
: _executor(executor),_task(std::move(task))
{
}
TaskAwaiter(TaskAwaiter &&completion) noexcept
: _executor(completion._executor),_task(std::exchange(completion._task, {}))
{
}
TaskAwaiter(TaskAwaiter &) = delete;
TaskAwaiter &operator=(TaskAwaiter &) = delete;
// constexpr的作用:
// 1. 可以在编译时计算出结果
// 2. 可以在编译时判断是否需要挂起
constexpr bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
// //_task执行完之后才调用resume,此时一定有值
// _task.finally([handle]()
// { handle.resume(); });
//改为调度器调度
_task.finally([handle, this]()
{
_executor->execute([handle]()
{
handle.resume();
});
});
}
// 这里是co_await之后的返回值
Result await_resume() noexcept
{
return _task.get_result();
}
private:
Task<Result,Executor>_task;
AbstractExecutor *_executor;
};
struct DispatchAwaiter
{
explicit DispatchAwaiter(AbstractExecutor *executor) noexcept
: _executor(executor)
{
}
bool await_ready() const
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) const
{
//调度到协程对应的调度器上
_executor->execute([handle]()
{
handle.resume();
});
}
void await_resume() const
{}
private:
AbstractExecutor *_executor;
};
template <typename ResultType, typename Executor>
struct TaskPromise
{
//协程启动时也需要在恢复时实现调度
DispatchAwaiter initial_suspend()
{
return DispatchAwaiter{&_executor};
}
std::suspend_always final_suspend() noexcept
{
return {};
}
Task<ResultType, Executor> get_return_object()
{
return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
template <typename _ResultType, typename _Executor>
TaskAwaiter<_ResultType, _Executor> await_transform(Task<_ResultType, _Executor> &&task)
{
return TaskAwaiter<_ResultType, _Executor>(&_executor, std::move(task));
}
void unhandled_exception()
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(std::current_exception()));
// 通知get_result当中的wait
_completion.notify_all();
// 调用回调
notify_callback();
}
void return_value(ResultType value)
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(value));
_completion.notify_all();
// 调用回调
notify_callback();
}
// 获取结果,若没有结果则阻塞等待--提供给Task使用
ResultType get_result()
{
// 如果_result没有值,则等待写入值之后调用notify_all
std::unique_lock lock(_completion_lock);
if (!_result.has_value())
{
// 等待写入值之后调用notify_all
_completion.wait(lock);
}
// 有值,则返回(或者抛出异常)
return _result->get_or_throw();
}
// 注册回调
void on_completed(std::function<void(Result<ResultType>)> &&func)
{
std::unique_lock lock(_completion_lock);
// 加锁判断result
if (_result.has_value())
{
//_result有值,则调用回调
auto value = _result.value();
// 解锁之后调用func
lock.unlock();
func(std::move(value));
}
else
{
// 否则添加回调函数,等待回调
_completion_callbacks.push_back(func);
}
}
private:
Executor _executor;
std::optional<Result<ResultType>> _result;
std::mutex _completion_lock;
std::condition_variable _completion;
// 回调列表,我们允许对同一个 Task 添加多个回调
std::list<std::function<void(Result<ResultType>)>> _completion_callbacks;
// 通知回调,调用回调函数,将结果传递给回调函数
void notify_callback()
{
auto value = _result.value();
for (auto &callback : _completion_callbacks)
{
callback(value);
}
// 调用完成,清空回调
_completion_callbacks.clear();
}
};
// NewThreadExecutor 是 AbstractExecutor 的子类,作为模板参数 Executor 的默认值
template<typename ResultType, typename Executor = NewThreadExecutor>
struct Task
{
using promise_type = TaskPromise<ResultType, Executor>;
ResultType get_result()
{
return _handle.promise().get_result();
}
// 如果有值,处理值
Task &then(std::function<void(ResultType)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
func(result.get_or_throw());
}
catch (std::exception &e)
{
// 忽略异常
} });
return *this;
}
// 如果有异常,处理异常
Task &catching(std::function<void(std::exception &)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
// 忽略返回值
result.get_or_throw();
}
catch (std::exception &e)
{
func(e);
} });
return *this;
}
Task &finally(std::function<void()> &&func)
{
_handle.promise().on_completed([func](auto result)
{ func(); });
return *this;
}
explicit Task(std::coroutine_handle<promise_type> handle) noexcept
: _handle(handle)
{
}
Task(Task &&task) noexcept
: _handle(std::exchange(task._handle, {}))
{
}
Task(Task &) = delete;
Task &operator=(Task &) = delete;
~Task()
{
if (_handle)
{
_handle.destroy();
}
}
private:
std::coroutine_handle<TaskPromise<ResultType, Executor>> _handle;
};
// test
Task<int, AsyncExecutor> simple_task2()
{
std::cout << "task 2 start ..." << std::endl;
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
std::cout << "task 2 return after 1s." << std::endl;
co_return 2;
}
Task<int, NewThreadExecutor> simple_task3()
{
std::cout << "task 3 start ..." << std::endl;
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
std::cout << "task 3 return after 2s." << std::endl;
co_return 3;
}
Task<int,LooperExecutor> simple_task()
{
//协程启动时就要调度到LooperExecutor上
std::cout << "task start ..." << std::endl;
auto result2 = co_await simple_task2();
//协程从simple_task2挂起后恢复执行,也要调度到LooperExecutor上
std::cout << "return from task2: " << result2 << std::endl;
auto result3 = co_await simple_task3();
//同上
std::cout << "return from task3: " << result3 << std::endl;
co_return 1 + result2 + result3;
}
int main()
{
auto simpleTask = simple_task();
simpleTask.then([](int i)
{ std::cout << "simple task end: " << i << std::endl; })
.catching([](std::exception &e)
{ std::cout << "error occurred " << e.what() << std::endl; });
try
{
auto i = simpleTask.get_result();
std::cout << "simple task end from get: " << i << std::endl;
}
catch (const std::exception &e)
{
std::cout << "error: " << e.what() << std::endl;
}
return 0;
}
cpp
//executor.hpp
#ifndef __EXECUTOR_HPP__
#define __EXECUTOR_HPP__
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
#include <optional>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <list>
#include <memory>
#include <chrono>
#include <thread>
#include <unistd.h>
#include <future>
#include <queue>
using namespace std;
//调度器抽象接口
class AbstractExecutor
{
public:
virtual void execute(std::function<void()> &&func) = 0;
};
// 无操作调度器,直接在当前线程执行
class NoopExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
func();
}
};
//创建新线程调度器,每次调度都创建一个新的线程,detach表示线程不受主线程控制,主线程可以继续执行
class NewThreadExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
std::thread(func).detach();
}
};
//异步调度器,每次调度都创建一个新的线程,async表示线程会在后台执行,主线程可以继续执行
//与NewThreadExecutor差不多,区别是调度时交给std::async处理,利用async背后的线程调度,提升线程的利用率
class AsyncExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
auto future = std::async(func);
}
};
//简单的单事件循环调度器
class LooperExecutor : public AbstractExecutor
{
private:
std::condition_variable _queue_condition;
std::mutex _queue_lock;
std::queue<std::function<void()>> _executable_queue;
//true为活动状态,false为停止状态
std::atomic<bool> _is_active;
//工作线程,用于处理事件循环
std::thread _work_thread;
//处理事件循环
void run_loop()
{
//检查当前事件循环是否处于工作状态,或者调度器队列没有清空
while(_is_active.load(std::memory_order_relaxed) || !_executable_queue.empty())
{
std::unique_lock lock(_queue_lock);
//如果队列为空,就需要等待新任务加入队列或者关闭事件循环的通知
if(_executable_queue.empty())
{
//队列为空,那么说明收到的是关闭的通知
_queue_condition.wait(lock);
//如果队列为空,那么说明收到的是关闭的通知
if(_executable_queue.empty())
{
//现有逻辑下此处break可以
//使用continue可以再次检查状态和队列,方便将来扩展
continue;
}
}
//取出任务,解锁,执行任务
auto func = _executable_queue.front();
_executable_queue.pop();
lock.unlock();
func();
}
}
public:
LooperExecutor()
{
_is_active.store(true, std::memory_order_relaxed);
_work_thread = std::thread(&LooperExecutor::run_loop, this);
}
~LooperExecutor()
{
shutdown(false);
//等待线程执行完,防止出现意外情况
join();
}
void execute(std::function<void()> &&func) override
{
std::unique_lock lock(_queue_lock);
if(_is_active.load(std::memory_order_relaxed))
{
_executable_queue.push(func);
lock.unlock();
//通知队列,主要用于队列之前为空时调用wait等待的情况
//通知不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_one();
}
}
//关闭事件循环,wait_for_complete表示是否等待所有任务执行完毕
void shutdown(bool wait_for_complete = true)
{
//修改后立即生效,在run_loop当中就能尽早(加锁前)就检测到_is_active的变化
_is_active.store(false, std::memory_order_relaxed);
if(!wait_for_complete)
{
std::unique_lock lock(_queue_lock);
//清空任务队列
decltype(_executable_queue) empty_queue;
std::swap(_executable_queue, empty_queue);
lock.unlock();
}
//通知wait函数,避免Looper线程不退出
//不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_all();
}
void join()
{
if(_work_thread.joinable())
{
_work_thread.join();
}
}
};
//共享事件循环调度器,使用单例模式,作用就是让各个协程共享一个LooperExecutor实例
//使用时直接调用execute方法即可
class SharedLooperExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
static LooperExecutor SharedLooperExecutor;
SharedLooperExecutor.execute(std::move(func));
}
};
#endif
bash
ubuntu@EVA:~/myfile/test$ g++ -o task task.cpp -std=c++20
ubuntu@EVA:~/myfile/test$ ./task
task start ...
task 2 start ...
task 2 return after 1s.
return from task2: 2
task 3 start ...
task 3 return after 2s.
return from task3: 3
simple task end: 6
simple task end from get: 6
基于协程的挂起实现无阻塞的sleep
实现目标:在以往,我们想要让程序等待 100ms,我们只能调用线程的 sleep 函数来阻塞当前线程 100ms。这样做确实可以让程序等待 100ms,但坏处就是这 100ms 期间,被阻塞的当前线程什么也做不了,白白占用了资源。协程出现之后,我们其实完全可以让协程在需要 sleep 的时候挂起,100ms 之后再来恢复执行,完全不需要阻塞当前线程。
想法不错,马上把用例给出来:
cpp
Task<int, AsyncExecutor> simple_task2()
{
std::cout << "task 2 start ..." << std::endl;
using namespace std::chrono_literals;
//之前的写法,用sleep_for让当前线程睡眠1秒
//1s是chrono_literals中的字面值写法,表示1秒
//std::this_thread::sleep_for(1s);
//改为使用co_await 1s;
co_await 1s;
std::cout << "task 2 return after 1s." << std::endl;
co_return 2;
}
sleep_for是让当前线程睡眠1秒,如果要让协程睡眠1秒,就需要使用到co_await。
什么是1s呢?其实这是C++11对字面值的一种支持,本质上就是一个运算符重载,这里的1s的类型就是duration;除了秒以外,时间单位也可以是毫秒、纳秒、分钟、小时等等,这些 C++ 11 的 duration 都已经提供了完善的支持,因此我们只要对 duration 做支持即可。
字面量运算符重载函数
C++ 中的字面量运算符重载(Literal Operator Overload)允许你为字面值常量定义自定义的后缀 ,从而让字面值能够自动转换为特定的类型或进行自定义处理。这个功能在 C++11 中引入,主要用于增强代码的可读性和编写便利性。
字面量运算符重载函数的定义以 operator"" 开头,后跟你自定义的后缀名。运算符重载本质上是一个具有特殊名称的函数。一般形式就是:
cpp
//注意operator""与自定义后缀之间需要一个空格,即为operator"" _s
//自定义后缀其实不用加_,不过加_是为了与库里面的字面量区分
返回类型 operator"" _自定义后缀(参数类型 参数) {
// 转换或处理逻辑
}
| 字面量类型 | 操作符函数可接受的参数类型 | 简单示例 |
|---|---|---|
| 整型 | unsigned long long 或 const char* |
123_n |
| 浮点型 | long double 或 const char* |
3.14_cm |
| 字符串 | (const char*, size_t) |
"hello"_s |
| 字符 | char |
'a'_c |
工作原理与细节:
- 编译时处理 :字面量运算符通常在编译期就会被解析和执行。例如,对于表达式123_n,编译器在编译时就会调用相应的operator"" _n函数,并将整数字面量123作为参数传入。
- 参数传递规则 :对于不同的字面量类型,编译器传递给操作符函数的参数是不同的:
- 整型和浮点型 的
const char*重载版本是一个"备选项"。只有当字面量的值过大,无法用unsigned long long或long double表示时,编译器才会选择调用const char*版本的函数,将字面量作为一个字符串传递给函数进行处理。 - 对于字符串字面量 ,函数会接收两个参数:一个指向字符串首地址的
const char*指针,以及一个表示字符串长度的size_t类型的值(不包含结尾的'\0')
- 整型和浮点型 的
- 非成员函数 :字面量运算符重载不能是类的成员函数 ,它必须被定义为全局函数或在命名空间内。
cpp
// 一个简单的标签类型
struct Tag {
unsigned int value;
};
// 字符串字面量运算符重载,将字符串转换为一个Tag对象
Tag operator"" _tag(const char* str, size_t len) {
// 这里可以进行简单的哈希计算或其他逻辑
unsigned int hash = 0;
for (size_t i = 0; i < len; ++i) {
hash = hash * 131 + str[i]; // 一个简单的哈希算法
}
return Tag{hash};
}
// 使用方式
Tag myTag = "configuration"_tag; // 编译器调用 operator"" _tag("configuration", 13)
好了,通过对字面量运算符重载的了解,我们可以知道1s是个什么东西了,1s的本质类型就是duration,因此我们需要对duration做支持。
为duration实现await_transfrom
在promise_type新增支持duration的await_transfrom成员函数:
cpp
template<typename ResultType, typename Executor>
struct TaskPromise
{
...
template<typename _Rep, typename _Period>
SleepAwaiter await_transform(std::chrono::duration<_Rep, _Period> &&duration)
{
return SleepAwaiter(&_executor, std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}
...
}
这里引入了一个新的类型 SleepAwaiter,它的任务有两个:
- 确保当前协程在若干毫秒之后恢复执行。
- 确保当前协程恢复执行时要调度到对应的调度器上。
std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() 实际上就是把任意单位的 duration 转换成毫秒。
SleepAwaiter的实现:
cpp
struct SleepAwaiter
{
explicit SleepAwaiter(AbstractExecutor *executor, long long duration) noexcept
:_executor(executor), _duration(duration)
{}
bool await_ready() const
{
return false;
}
void await_suspend(std::coroutine_handle<> handle)
{
//自定义的定时任务调度器,全局只需要一个实例
static Scheduler scheduler;
scheduler.execute([this,handle]()
{
//_duration毫秒之后执行下面的代码
_executor->execute([handle]()
{
handle.resume();
});
}, _duration);
}
void await_resume()
{}
private:
AbstractExecutor *_executor;
long long _duration; // 等待的时间,单位为毫秒
};
其中最关键的地方就是定时任务调度器Scheduler的实现了。
定时任务调度器Scheduler
定时任务调度器,本质上就是一个时间管理大师。任何交给它的任务都需要有优先级,优先级的计算规则当然就是延时的长短,于是我们需要用到优先级队列来存储待执行的任务。
说到任务队列就要想起上一篇文章当中的 LooperExecutor,如果我们给它加上计时执行的能力,Scheduler 的功能就差不多完成了。换个角度看,LooperExecutor 其实就是 Scheduler 的一个特化版本,它的所有任务的延时都是 0。
定义定时任务的描述类型
为了方便管理定时任务,我们需要定义一个类型DelayExecutable,包含要执行时的绝对时间和多个相关函数。同时还需要定义一个仿函数,供给优先队列作为优先策略。
cpp
//定时任务描述--包含一个函数和它要执行的绝对时间
class DelayedExecutable
{
public:
DelayedExecutable(std::function<void()> &&func, long long delay)
:_func(std::move(func))
{
using namespace std;
using namespace std::chrono;
auto now = system_clock::now();
//当前时间戳,单位毫秒
auto current = duration_cast<milliseconds>(now.time_since_epoch()).count();
//计算出任务的计划执行时间
_scheduled_time = current + delay;
}
//调用时,返回从当前时间还需要多少毫秒到任务执行时间
long long delay() const
{
using namespace std;
using namespace std::chrono;
auto now = system_clock::now();
auto current = duration_cast<milliseconds>(now.time_since_epoch()).count();
return _scheduled_time - current;
}
//返回执行时间
long long get_scheduled_time() const
{
return _scheduled_time;
}
//执行任务
void operator()()
{
_func();
}
private:
long long _scheduled_time;
std::function<void()> _func;
};
//DelayedExecutable比较仿函数
class DelayedExecutableCompare
{
public:
bool operator() (DelayedExecutable &left, DelayedExecutable &right)
{
//按照任务执行时间升序排列
return left.get_scheduled_time() > right.get_scheduled_time();
}
};
实现定时任务调度器
具体见代码和注释:
//定时任务调度器
class Scheduler
{
private:
std::condition_variable _queue_condition;
std::mutex _queue_lock;
//注意这里改用优先级队列--小顶堆--greater,所以DelayExecutableCompare要用>符号
std::priority_queue<DelayedExecutable, std::vector<DelayedExecutable>, DelayedExecutableCompare> _executable_queue;
std::atomic<bool> _is_active;
std::thread _work_thread;
void run_loop()
{
while(_is_active.load(std::memory_order_relaxed) || !_executable_queue.empty())
{
std::unique_lock lock(_queue_lock);
if(_executable_queue.empty())
{
_queue_condition.wait(lock);
if(_executable_queue.empty())
{
continue;
}
}
//需要判断最先执行的任务是否到时间了
auto executable = _executable_queue.top();
long long delay = executable.delay();
if(delay > 0)
{
//队头任务还没到时间,再等待delay毫秒
auto status = _queue_condition.wait_for(lock, std::chrono::milliseconds(delay));
//如果等待期间没有延时比 delay 更小的任务加入,这里就会返回 timeout
//如果有小于 delay 毫秒的任务加入,wait被唤醒,这里就会返回 not_timeout
if(status != std::cv_status::timeout)
{
// 不是 timeout,需要重新计算队头的延时
continue;
}
}
_executable_queue.pop();
lock.unlock();
executable();//执行任务--operator()
}
}
public:
Scheduler()
{
_is_active.store(true, std::memory_order_relaxed);
_work_thread = std::thread(&Scheduler::run_loop, this);
}
~Scheduler()
{
shutdown(false);
//等待线程执行完,防止出现意外情况
join();
}
void execute(std::function<void()> &&func, long long delay)
{
delay = delay < 0 ? 0 : delay;
std::unique_lock lock(_queue_lock);
if(_is_active.load(std::memory_order_relaxed))
{
// 只有队列为空或者比当前队头任务的延时更小时,需要调用 notify_one
// 其他情况只需要按顺序依次执行即可
bool need_notify = _executable_queue.empty() || _executable_queue.top().delay() > delay;
_executable_queue.push(DelayedExecutable(std::move(func), delay));
lock.unlock();
if (need_notify)
{
_queue_condition.notify_one();
}
}
}
//关闭事件循环,wait_for_complete表示是否等待所有任务执行完毕
void shutdown(bool wait_for_complete = true)
{
//修改后立即生效,在run_loop当中就能尽早(加锁前)就检测到_is_active的变化
_is_active.store(false, std::memory_order_relaxed);
if(!wait_for_complete)
{
std::unique_lock lock(_queue_lock);
//清空任务队列
decltype(_executable_queue) empty_queue;
std::swap(_executable_queue, empty_queue);
lock.unlock();
}
//通知wait函数,避免线程不退出
//不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_all();
}
void join()
{
if(_work_thread.joinable())
{
_work_thread.join();
}
}
};
通过对代码和注释的阅读,相信大家能够明白延时的实现其实是通过阻塞一个专门用于调度延时任务的线程来做到的。
相信有读者会有疑问:这不还是有阻塞吗?
没错,阻塞是免不了的。通常而言,我们也不会用一个线程去严格对应一个协程,当一个协程挂起时,执行这个协程的线程就会被空闲出来有机会去调度执行其他协程,进而让线程的利用率得到充分提升。如果有 10 个协程都需要执行延时,相较于阻塞这 10 个协程当前所在的 10 个线程而言,阻塞一个线程显然是更加经济的。
完整代码和运行结果
cpp
//executor.hpp
#ifndef __EXECUTOR_HPP__
#define __EXECUTOR_HPP__
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
#include <optional>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <list>
#include <memory>
#include <chrono>
#include <thread>
#include <unistd.h>
#include <future>
#include <queue>
using namespace std;
//调度器抽象接口
class AbstractExecutor
{
public:
virtual void execute(std::function<void()> &&func) = 0;
};
// 无操作调度器,直接在当前线程执行
class NoopExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
func();
}
};
//创建新线程调度器,每次调度都创建一个新的线程,detach表示线程不受主线程控制,主线程可以继续执行
class NewThreadExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
std::thread(func).detach();
}
};
//异步调度器,每次调度都创建一个新的线程,async表示线程会在后台执行,主线程可以继续执行
//与NewThreadExecutor差不多,区别是调度时交给std::async处理,利用async背后的线程调度,提升线程的利用率
class AsyncExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
auto future = std::async(func);
}
};
//简单的单事件循环调度器
class LooperExecutor : public AbstractExecutor
{
private:
std::condition_variable _queue_condition;
std::mutex _queue_lock;
std::queue<std::function<void()>> _executable_queue;
//true为活动状态,false为停止状态
std::atomic<bool> _is_active;
//工作线程,用于处理事件循环
std::thread _work_thread;
//处理事件循环
void run_loop()
{
//检查当前事件循环是否处于工作状态,或者调度器队列没有清空
while(_is_active.load(std::memory_order_relaxed) || !_executable_queue.empty())
{
std::unique_lock lock(_queue_lock);
//如果队列为空,就需要等待新任务加入队列或者关闭事件循环的通知
if(_executable_queue.empty())
{
//队列为空,那么说明收到的是关闭的通知
_queue_condition.wait(lock);
//如果队列为空,那么说明收到的是关闭的通知
if(_executable_queue.empty())
{
//现有逻辑下此处break可以
//使用continue可以再次检查状态和队列,方便将来扩展
continue;
}
}
//取出任务,解锁,执行任务
auto func = _executable_queue.front();
_executable_queue.pop();
lock.unlock();
func();
}
}
public:
LooperExecutor()
{
_is_active.store(true, std::memory_order_relaxed);
_work_thread = std::thread(&LooperExecutor::run_loop, this);
}
~LooperExecutor()
{
shutdown(false);
//等待线程执行完,防止出现意外情况
join();
}
void execute(std::function<void()> &&func) override
{
std::unique_lock lock(_queue_lock);
if(_is_active.load(std::memory_order_relaxed))
{
_executable_queue.push(func);
lock.unlock();
//通知队列,主要用于队列之前为空时调用wait等待的情况
//通知不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_one();
}
}
//关闭事件循环,wait_for_complete表示是否等待所有任务执行完毕
void shutdown(bool wait_for_complete = true)
{
//修改后立即生效,在run_loop当中就能尽早(加锁前)就检测到_is_active的变化
_is_active.store(false, std::memory_order_relaxed);
if(!wait_for_complete)
{
std::unique_lock lock(_queue_lock);
//清空任务队列
decltype(_executable_queue) empty_queue;
std::swap(_executable_queue, empty_queue);
lock.unlock();
}
//通知wait函数,避免Looper线程不退出
//不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_all();
}
void join()
{
if(_work_thread.joinable())
{
_work_thread.join();
}
}
};
//共享事件循环调度器,使用单例模式,作用就是让各个协程共享一个LooperExecutor实例
//使用时直接调用execute方法即可
class SharedLooperExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
static LooperExecutor SharedLooperExecutor;
SharedLooperExecutor.execute(std::move(func));
}
};
//定时任务描述--包含一个函数和它要执行的绝对时间
class DelayedExecutable
{
public:
DelayedExecutable(std::function<void()> &&func, long long delay)
:_func(std::move(func))
{
using namespace std;
using namespace std::chrono;
auto now = system_clock::now();
//当前时间戳,单位毫秒
auto current = duration_cast<milliseconds>(now.time_since_epoch()).count();
//计算出任务的计划执行时间
_scheduled_time = current + delay;
}
//调用时,返回从当前时间还需要多少毫秒到任务执行时间
long long delay() const
{
using namespace std;
using namespace std::chrono;
auto now = system_clock::now();
auto current = duration_cast<milliseconds>(now.time_since_epoch()).count();
return _scheduled_time - current;
}
//返回执行时间
long long get_scheduled_time() const
{
return _scheduled_time;
}
//执行任务
void operator()()
{
_func();
}
private:
long long _scheduled_time;
std::function<void()> _func;
};
//DelayedExecutable比较仿函数
class DelayedExecutableCompare
{
public:
bool operator() (DelayedExecutable &left, DelayedExecutable &right)
{
//按照任务执行时间升序排列
return left.get_scheduled_time() > right.get_scheduled_time();
}
};
//定时任务调度器
class Scheduler
{
private:
std::condition_variable _queue_condition;
std::mutex _queue_lock;
//注意这里改用优先级队列--小顶堆--greater,所以DelayExecutableCompare要用>符号
std::priority_queue<DelayedExecutable, std::vector<DelayedExecutable>, DelayedExecutableCompare> _executable_queue;
std::atomic<bool> _is_active;
std::thread _work_thread;
void run_loop()
{
while(_is_active.load(std::memory_order_relaxed) || !_executable_queue.empty())
{
std::unique_lock lock(_queue_lock);
if(_executable_queue.empty())
{
_queue_condition.wait(lock);
if(_executable_queue.empty())
{
continue;
}
}
//需要判断最先执行的任务是否到时间了
auto executable = _executable_queue.top();
long long delay = executable.delay();
if(delay > 0)
{
//队头任务还没到时间,再等待delay毫秒
auto status = _queue_condition.wait_for(lock, std::chrono::milliseconds(delay));
//如果等待期间没有延时比 delay 更小的任务加入,这里就会返回 timeout
//如果有小于 delay 毫秒的任务加入,wait被唤醒,这里就会返回 not_timeout
if(status != std::cv_status::timeout)
{
// 不是 timeout,需要重新计算队头的延时
continue;
}
}
_executable_queue.pop();
lock.unlock();
executable();//执行任务--operator()
}
}
public:
Scheduler()
{
_is_active.store(true, std::memory_order_relaxed);
_work_thread = std::thread(&Scheduler::run_loop, this);
}
~Scheduler()
{
shutdown(false);
//等待线程执行完,防止出现意外情况
join();
}
void execute(std::function<void()> &&func, long long delay)
{
delay = delay < 0 ? 0 : delay;
std::unique_lock lock(_queue_lock);
if(_is_active.load(std::memory_order_relaxed))
{
// 只有队列为空或者比当前队头任务的延时更小时,需要调用 notify_one
// 其他情况只需要按顺序依次执行即可
bool need_notify = _executable_queue.empty() || _executable_queue.top().delay() > delay;
_executable_queue.push(DelayedExecutable(std::move(func), delay));
lock.unlock();
if (need_notify)
{
_queue_condition.notify_one();
}
}
}
//关闭事件循环,wait_for_complete表示是否等待所有任务执行完毕
void shutdown(bool wait_for_complete = true)
{
//修改后立即生效,在run_loop当中就能尽早(加锁前)就检测到_is_active的变化
_is_active.store(false, std::memory_order_relaxed);
if(!wait_for_complete)
{
std::unique_lock lock(_queue_lock);
//清空任务队列
decltype(_executable_queue) empty_queue;
std::swap(_executable_queue, empty_queue);
lock.unlock();
}
//通知wait函数,避免线程不退出
//不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_all();
}
void join()
{
if(_work_thread.joinable())
{
_work_thread.join();
}
}
};
#endif
cpp
//task.cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
#include <optional>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <list>
#include <memory>
#include <chrono>
#include <thread>
#include <unistd.h>
#include "executor.hpp"
using namespace std;
template<typename Result, typename Executor>
struct TaskAwaiter;
template <typename ResultType, typename Executor>
struct TaskPromise;
template<typename ResultType, typename Executor>
struct Task;
template <typename T>
struct Result
{
// 初始化为默认值
explicit Result() = default;
// 当Task正常返回时用结果初始化Result
explicit Result(T &&value)
: _value(std::move(value))
{
}
// 当Task抛出异常时用异常初始化Result
explicit Result(std::exception_ptr &&exception_ptr)
: _exception_ptr(std::move(exception_ptr))
{
}
// 读取结果,有异常则抛出异常
T get_or_throw()
{
if (_exception_ptr)
{
// 重新抛出
std::rethrow_exception(_exception_ptr);
}
return _value;
}
private:
T _value;
std::exception_ptr _exception_ptr;
};
// 调度器的类型有多种,因此专门提供一个模板参数 Executor
template<typename Result, typename Executor>
struct TaskAwaiter
{
explicit TaskAwaiter(AbstractExecutor *executor, Task<Result, Executor> &&task) noexcept
: _executor(executor),_task(std::move(task))
{
}
TaskAwaiter(TaskAwaiter &&completion) noexcept
: _executor(completion._executor),_task(std::exchange(completion._task, {}))
{
}
TaskAwaiter(TaskAwaiter &) = delete;
TaskAwaiter &operator=(TaskAwaiter &) = delete;
// constexpr的作用:
// 1. 可以在编译时计算出结果
// 2. 可以在编译时判断是否需要挂起
constexpr bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
// //_task执行完之后才调用resume,此时一定有值
// _task.finally([handle]()
// { handle.resume(); });
//改为调度器调度
_task.finally([handle, this]()
{
_executor->execute([handle]()
{
handle.resume();
});
});
}
// 这里是co_await之后的返回值
Result await_resume() noexcept
{
return _task.get_result();
}
private:
Task<Result,Executor>_task;
AbstractExecutor *_executor;
};
struct DispatchAwaiter
{
explicit DispatchAwaiter(AbstractExecutor *executor) noexcept
: _executor(executor)
{
}
bool await_ready() const
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) const
{
//调度到协程对应的调度器上
_executor->execute([handle]()
{
handle.resume();
});
}
void await_resume() const
{}
private:
AbstractExecutor *_executor;
};
struct SleepAwaiter
{
explicit SleepAwaiter(AbstractExecutor *executor, long long duration) noexcept
:_executor(executor), _duration(duration)
{}
bool await_ready() const
{
return false;
}
void await_suspend(std::coroutine_handle<> handle)
{
//自定义的定时任务调度器,全局只需要一个实例
static Scheduler scheduler;
scheduler.execute([this,handle]()
{
//_duration毫秒之后执行下面的代码
_executor->execute([handle]()
{
handle.resume();
});
}, _duration);
}
void await_resume()
{}
private:
AbstractExecutor *_executor;
long long _duration; // 等待的时间,单位为毫秒
};
template <typename ResultType, typename Executor>
struct TaskPromise
{
//协程启动时也需要在恢复时实现调度
DispatchAwaiter initial_suspend()
{
return DispatchAwaiter{&_executor};
}
std::suspend_always final_suspend() noexcept
{
return {};
}
Task<ResultType, Executor> get_return_object()
{
return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
template <typename _ResultType, typename _Executor>
TaskAwaiter<_ResultType, _Executor> await_transform(Task<_ResultType, _Executor> &&task)
{
return TaskAwaiter<_ResultType, _Executor>(&_executor, std::move(task));
}
template<typename _Rep, typename _Period>
SleepAwaiter await_transform(std::chrono::duration<_Rep, _Period> &&duration)
{
return SleepAwaiter(&_executor, std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}
void unhandled_exception()
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(std::current_exception()));
// 通知get_result当中的wait
_completion.notify_all();
// 调用回调
notify_callback();
}
void return_value(ResultType value)
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(value));
_completion.notify_all();
// 调用回调
notify_callback();
}
// 获取结果,若没有结果则阻塞等待--提供给Task使用
ResultType get_result()
{
// 如果_result没有值,则等待写入值之后调用notify_all
std::unique_lock lock(_completion_lock);
if (!_result.has_value())
{
// 等待写入值之后调用notify_all
_completion.wait(lock);
}
// 有值,则返回(或者抛出异常)
return _result->get_or_throw();
}
// 注册回调
void on_completed(std::function<void(Result<ResultType>)> &&func)
{
std::unique_lock lock(_completion_lock);
// 加锁判断result
if (_result.has_value())
{
//_result有值,则调用回调
auto value = _result.value();
// 解锁之后调用func
lock.unlock();
func(std::move(value));
}
else
{
// 否则添加回调函数,等待回调
_completion_callbacks.push_back(func);
}
}
private:
Executor _executor;
std::optional<Result<ResultType>> _result;
std::mutex _completion_lock;
std::condition_variable _completion;
// 回调列表,我们允许对同一个 Task 添加多个回调
std::list<std::function<void(Result<ResultType>)>> _completion_callbacks;
// 通知回调,调用回调函数,将结果传递给回调函数
void notify_callback()
{
auto value = _result.value();
for (auto &callback : _completion_callbacks)
{
callback(value);
}
// 调用完成,清空回调
_completion_callbacks.clear();
}
};
// NewThreadExecutor 是 AbstractExecutor 的子类,作为模板参数 Executor 的默认值
template<typename ResultType, typename Executor = NewThreadExecutor>
struct Task
{
using promise_type = TaskPromise<ResultType, Executor>;
ResultType get_result()
{
return _handle.promise().get_result();
}
// 如果有值,处理值
Task &then(std::function<void(ResultType)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
func(result.get_or_throw());
}
catch (std::exception &e)
{
// 忽略异常
} });
return *this;
}
// 如果有异常,处理异常
Task &catching(std::function<void(std::exception &)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
// 忽略返回值
result.get_or_throw();
}
catch (std::exception &e)
{
func(e);
} });
return *this;
}
Task &finally(std::function<void()> &&func)
{
_handle.promise().on_completed([func](auto result)
{ func(); });
return *this;
}
explicit Task(std::coroutine_handle<promise_type> handle) noexcept
: _handle(handle)
{
}
Task(Task &&task) noexcept
: _handle(std::exchange(task._handle, {}))
{
}
Task(Task &) = delete;
Task &operator=(Task &) = delete;
~Task()
{
if (_handle)
{
_handle.destroy();
}
}
private:
std::coroutine_handle<TaskPromise<ResultType, Executor>> _handle;
};
// test1 ////////////////////////////////////////////////////////////////
// Task<int, AsyncExecutor> simple_task2()
// {
// std::cout << "task 2 start ..." << std::endl;
// using namespace std::chrono_literals;
// std::this_thread::sleep_for(1s);
// std::cout << "task 2 return after 1s." << std::endl;
// co_return 2;
// }
// Task<int, NewThreadExecutor> simple_task3()
// {
// std::cout << "task 3 start ..." << std::endl;
// using namespace std::chrono_literals;
// std::this_thread::sleep_for(2s);
// std::cout << "task 3 return after 2s." << std::endl;
// co_return 3;
// }
// Task<int,LooperExecutor> simple_task()
// {
// //协程启动时就要调度到LooperExecutor上
// std::cout << "task start ..." << std::endl;
// auto result2 = co_await simple_task2();
// //协程从simple_task2挂起后恢复执行,也要调度到LooperExecutor上
// std::cout << "return from task2: " << result2 << std::endl;
// auto result3 = co_await simple_task3();
// //同上
// std::cout << "return from task3: " << result3 << std::endl;
// co_return 1 + result2 + result3;
// }
// int main()
// {
// auto simpleTask = simple_task();
// simpleTask.then([](int i)
// { std::cout << "simple task end: " << i << std::endl; })
// .catching([](std::exception &e)
// { std::cout << "error occurred " << e.what() << std::endl; });
// try
// {
// auto i = simpleTask.get_result();
// std::cout << "simple task end from get: " << i << std::endl;
// }
// catch (const std::exception &e)
// {
// std::cout << "error: " << e.what() << std::endl;
// }
// return 0;
// }
// test2 ////////////////////////////////////////////////////////////////
Task<int, AsyncExecutor> simple_task2()
{
std::cout << "task 2 start ..." << std::endl;
using namespace std::chrono_literals;
//之前的写法,用sleep_for让当前线程睡眠1秒
//1s是chrono_literals中的字面值写法,表示1秒
//std::this_thread::sleep_for(1s);
//改为使用co_await 1s;
co_await 1s;
std::cout << "task 2 return after 1s." << std::endl;
co_return 2;
}
Task<int, NewThreadExecutor> simple_task3()
{
std::cout << "task 3 start ..." << std::endl;
// using namespace std::chrono_literals;
// std::this_thread::sleep_for(2s);
//改为使用co_await 2s;
co_await 2s;
std::cout << "task 3 return after 2s." << std::endl;
co_return 3;
}
Task<int,LooperExecutor> simple_task()
{
//协程启动时就要调度到LooperExecutor上
std::cout << "task start ..." << std::endl;
auto result2 = co_await simple_task2();
//协程从simple_task2挂起后恢复执行,也要调度到LooperExecutor上
std::cout << "return from task2: " << result2 << std::endl;
auto result3 = co_await simple_task3();
//同上
std::cout << "return from task3: " << result3 << std::endl;
co_return 1 + result2 + result3;
}
void testTask()
{
auto simpleTask = simple_task();
simpleTask.then([](int i)
{ std::cout << "simple task end: " << i << std::endl; })
.catching([](std::exception &e)
{ std::cout << "error occurred " << e.what() << std::endl; });
try
{
auto i = simpleTask.get_result();
std::cout << "simple task end from get: " << i << std::endl;
}
catch (const std::exception &e)
{
std::cout << "error: " << e.what() << std::endl;
}
}
void testScheduler()
{
auto scheduler = Scheduler();
std::cout << "start" << std::endl;
scheduler.execute([](){std::cout << "2" << std::endl;}, 100);
scheduler.execute([](){std::cout << "1" << std::endl;}, 50);
scheduler.execute([](){std::cout << "6" << std::endl;}, 1000);
scheduler.execute([](){std::cout << "5" << std::endl;}, 500);
scheduler.execute([](){std::cout << "3" << std::endl;}, 200);
scheduler.execute([](){std::cout << "4" << std::endl;}, 300);
scheduler.shutdown();
scheduler.join();
}
int main()
{
testTask();
//testScheduler();
return 0;
}
testTask():

testScheduler():

用于协程之间消息传递的Channel
什么是Channel?
Channel是通道,是作为数据传输的管道,用于在不同执行单元(如线程、协程、进程、或者网络连接的两端)之间安全、高效地传递信息。
C++ 标准库目前没有直接提供 Channel 原语,但我们可以利用标准库中的 std::mutex, std::condition_variable, std::queue 等组件来实现一个基本的有缓冲 Channel;
例如:一个简单生产消费模型,使用队列作为缓冲区、互斥锁保护共享队列、生产者条件变量等待队列不满、消费者条件变量等待队列不空,代码如下:
cpp
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>
//一个简单的有缓冲Channel模板类
template<typename T>
class Channel
{
private:
std::queue<T> _queue;
std::mutex _mtx;
std::condition_variable _cv_producer;
std::condition_variable _cv_consumer;
size_t _capacity;
bool _closed = false;
public:
explicit Channel(size_t capacity = 0)
: _capacity(capacity)
{}
bool send(const T& value)
{
std::unique_lock<std::mutex> lock(_mtx);
//条件满足时继续执行,不满足就阻塞
_cv_producer.wait(lock,[this]()
{
return _closed || (_capacity == 0 && _queue.empty()) || _queue.size() < _capacity;
});
if(_closed)
{
//通道关闭,发送失败
return false;
}
_queue.push(value);
//通知一个等待的消费者
_cv_consumer.notify_one();
return true;
}
bool receive(T& value)
{
std::unique_lock<std::mutex> lock(_mtx);
//条件满足时继续执行,不满足就阻塞
_cv_consumer.wait(lock,[this]()
{
return _closed || !_queue.empty();
});
if(_closed && _queue.empty())
{
//通道关闭,且队列为空,接收失败
return false;
}
value = _queue.front();
_queue.pop();
//通知一个等待的生产者
_cv_producer.notify_one();
return true;
}
//关闭通道
void close()
{
std::unique_lock<std::mutex> lock(_mtx);
_closed = true;
//通知所有等待的线程
_cv_producer.notify_all();
_cv_consumer.notify_all();
}
};
// 示例用法:生产者-消费者模型
int main()
{
Channel<int> ch(10); // 创建一个容量为 10 的通道
// 生产者线程
std::thread producer([&ch]()
{
for (int i = 0; i < 5; ++i)
{
if(ch.send(i))
{
std::cout << "Sent: " << i << std::endl;
}
}
ch.close(); // 生产完毕,关闭通道
});
// 消费者线程
std::thread consumer([&ch]()
{
int value;
while (ch.receive(value))
{ // 只要通道未关闭或还有数据,就持续接收
std::cout << "Received: " << value << std::endl;
}
});
producer.join();
consumer.join();
return 0;
}
C++协程的Channel实现设计
C++20 引入了协程(Coroutines),为异步编程提供了更强大的支持。在此基础上,可以实现与 Go 语言的 chan 或 Rust 的 channel 语义更接近的 Channel,能够使用 co_await 进行异步的发送和接收,让并发代码写起来更加简洁直观。
我们最终实现的Channel的用例:
cpp
Task<void, LooperExecutor> Producer(Channel<int> &channel)
{
int i = 0;
while(i < 10)
{
//写入时调用write函数
co_await channel.write(i++);
//或者使用 << 运算符
co_await (channel << i++);
}
//支持关闭
channel.close();
}
Task<void, LooperExecutor> Consumer(Channel<int> &channel)
{
while(channel.is_active())
{
try
{
//读取时使用read函数,表达式的值就是读取的值
auto received = co_await channel.read();
int received;
//或者使用 >> 运算符
co_await (channel >> received);
}
catch(const std::exception &e)
{
std::cout << "exception: " << e.what() << std::endl;
}
}
}
我们的 Channel 也可以在构造的时候传入 buffer 的大小,默认没有 buffer。
co_await表达式的支持
想要支持 co_await 表达式,只需要为 Channel 读写函数返回的Awaiter 类型添加相应的 await_transform 函数。我们姑且认为read 和 write 两个函数的返回值类型 ReaderAwaiter 和WriterAwaiter,接下来就添加一个非常简单的 await_transform 的支持:
cpp
template <typename ResultType, typename Executor>
struct TaskPromise
{
...
template<typename _ValueType>
auto await_transform(ReaderAwaiter<_ValueType> reader_awaiter)
{
reader_awaiter.executor = &_executor;
return reader_awaiter;
}
template<typename _ValueType>
auto await_transform(WriterAwaiter<_ValueType> writer_awaiter)
{
writer_awaiter.executor = &_executor;
return writer_awaiter;
}
...
};
由于 Channel 的 buffer 和对 Channel 的读写本身会决定协程是否挂起或恢复(buffer满了或者空了需要挂起) ,因此这些逻辑我们都将在 Channel 当中给出 ,TaskPromise 能做的就是把调度器传过去,当协程恢复时使用。
Awaiter的实现
Awaiter 负责在挂起时将自己存入 Channel,并且在需要时恢复协程。因此除了前面看到需要在恢复执行协程时的调度器之外,Awaiter 还需要持有 Channel、需要读写的值。
简单说来,Awaiter 的功能就是:
- 负责用协程的调度器在需要时恢复协程
- 处理读写的值的传递
WriterAwaiter的实现:
cpp
template <typename ValueType>
struct WriterAwaiter
{
WriterAwaiter(Channel<ValueType> *channel, ValueType value)
: _channel(channel), _value(value)
{
}
WriterAwaiter(WriterAwaiter &&other) noexcept
: _channel(std::exchange(other._channel, nullptr)),
_executor(std::exchange(other._executor, nullptr)),
_value(other._value),
_handle(other._handle)
{
}
bool await_ready()
{
return false;
}
auto await_suspend(std::coroutine_handle<> coroutine_handle)
{
// 记录协程handle,恢复时用
_handle = coroutine_handle;
// 将自身传给Channel,Channel内部会根据自身状态处理是否立即恢复或者挂起
_channel->try_push_writer(this);
}
void await_resume()
{
// Channel 关闭时也会将挂起的读写协程恢复
// 要检查是否是关闭引起的恢复,如果是,check_closed 会抛出 Channel 关闭异常
_channel->check_closed();
_channel = nullptr;
}
// Channel当中恢复该协程时调用resume函数
void resume()
{
// 将调度器调度的逻辑封装在这里
if (_executor)
{
_executor->execute([this]()
{ _handle.resume(); });
}
else
{
_handle.resume();
}
}
ValueType value() const
{
return _value;
}
void set_executor(AbstractExecutor *executor)
{
if(executor)
{
_executor = executor;
}
}
~WriterAwaiter()
{
//_channel不为空,说明协程提前销毁了
// 调用channel的remove_reader将自己直接移除
if (_channel)
_channel->remove_writer(this);
}
private:
Channel<ValueType> *_channel;
AbstractExecutor *_executor = nullptr;
ValueType _value;
std::coroutine_handle<> _handle;
};
还有ReaderAwaiter,实现类似:
cpp
template <typename ValueType>
struct ReaderAwaiter
{
explicit ReaderAwaiter(Channel<ValueType> *channel)
: _channel(channel)
{
}
// 实现移动构造函数,主要目的是原对象的channel置为空
ReaderAwaiter(ReaderAwaiter &&other) noexcept
: _channel(std::exchange(other._channel, nullptr)),
_executor(std::exchange(other._executor, nullptr)),
_value(other._value),
_p_value(std::exchange(other._p_value, nullptr)),
_handle(other._handle)
{
}
bool await_ready()
{
return false;
}
auto await_suspend(std::coroutine_handle<> coroutine_handle)
{
_handle = coroutine_handle;
// 将自身传给Channel,Channel内部会根据自身状态处理是否立即恢复或者挂起
_channel->try_push_reader(this);
}
int await_resume()
{
// Channel 关闭时也会将挂起的读写协程恢复
// 要检查是否是关闭引起的恢复,如果是,check_closed 会抛出 Channel 关闭异常
_channel->check_closed();
// 协程恢复,channel已经没用了,置空
_channel = nullptr;
// 读取到的值
return _value;
}
// Channel当中恢复该协程时调用resume函数
void resume(ValueType value)
{
_value = value;
if (_p_value)
{
*_p_value = value;
}
resume();
}
// Channel 关闭时调用 resume() 函数来恢复该协程
// 在 await_resume 当中,如果 Channel 关闭,会抛出 Channel 关闭异常
void resume()
{
if (_executor)
{
_executor->execute([this]()
{ _handle.resume(); });
}
else
{
_handle.resume();
}
}
void set_p_value(ValueType *p_value)
{
_p_value = p_value;
}
void set_executor(AbstractExecutor *executor)
{
if(executor)
{
_executor = executor;
}
}
~ReaderAwaiter()
{
//_channel不为空,说明协程提前销毁了
// 调用channel的remove_reader函数将自己直接移除
if (_channel)
_channel->remove_reader(this);
}
private:
Channel<ValueType> *_channel;
AbstractExecutor *_executor = nullptr;
ValueType _value;
// 用于 channel >> received; 这种情况
// 需要将变量的地址传入,协程恢复时写入变量内存
ValueType *_p_value = nullptr;
std::coroutine_handle<> _handle;
};
实现Channel
Channel需要实现基础的缓存buffer、状态标识、锁和条件变量外,还需要实现两个等待链表分别存储新来的且需要等待的写入者和读取者,当缓存满足条件时再恢复。
除此之外,还需要实现read和write函数以及他们的运算符重载(>>和<<),这几个函数作用就是把Awaiter创建出来,然后填充信息再返回。
最重要的是try_push_writer和try_push_reader:
try_push_writer 调用时,意味着有一个新的写入者挂起准备写入值到 Channel 当中,这时候有以下几种情况:
Channel当中有挂起的读取者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者Channel的 buffer 没满,写入者把值写入 buffer,然后立即恢复执行。Channel的 buffer 已满,则写入者被存入挂起列表(writer_list)等待新的读取者读取时再恢复。
try_push_reader 调用时,意味着有一个新的读取者挂起准备从 Channel 当中读取值,这时候有以下几种情况:
Channel的 buffer 非空,读取者从 buffer 当中读取值,如果此时有挂起的写入者,需要去队头的写入者将值写入 buffer,然后立即恢复该写入者和当次的读取者。Channel当中有挂起的写入者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者Channel的 buffer 为空,则读取者被存入挂起列表(reader_list)等待新的写入者写入时再恢复。
实现如下:
cpp
template <typename ValueType>
struct Channel
{
auto write(ValueType value)
{
check_closed();
return WriterAwaiter<ValueType>(this, value);
}
auto operator<<(ValueType value)
{
return write(value); // 复用
}
auto read()
{
check_closed();
return ReaderAwaiter<ValueType>(this);
}
auto operator>>(ValueType &value_ref)
{
auto awaiter = read();
// 保存待赋值的变量的地址,方便后续写入
awaiter.set_p_value(&value_ref);
return awaiter;
}
// try_push_writer 调用时,意味着有一个新的写入者挂起准备写入值到 Channel 当中,这时候有以下几种情况:
// 1、Channel 当中有挂起的读取者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者
// 2、Channel 的 buffer 没满,写入者把值写入 buffer,然后立即恢复执行。
// 3、Channel 的 buffer 已满,则写入者被存入挂起列表(writer_list)等待新的读取者读取时再恢复。
void try_push_writer(WriterAwaiter<ValueType> *writer_awaiter)
{
std::unique_lock lock(_channel_lock);
check_closed();
// 1、检查有没有挂起的读取者
if (!_reader_list.empty())
{
auto reader = _reader_list.front();
_reader_list.pop_front();
lock.unlock();
reader->resume(writer_awaiter->value());
writer_awaiter->resume();
return;
}
// 2、buffer 没满,写入buffer
if (_buffer.size() < _buffer_capacity)
{
_buffer.push(writer_awaiter->value());
lock.unlock();
writer_awaiter->resume();
return;
}
// 3、buffer 已满,写入者被存入挂起列表(writer_list)等待新的读取者读取时再恢复。
_writer_list.push_back(writer_awaiter);
}
// 相对应的,try_push_reader 调用时,意味着有一个新的读取者挂起准备从 Channel 当中读取值,这时候有以下几种情况:
// 1、Channel 的 buffer 非空,读取者从 buffer 当中读取值,如果此时有挂起的写入者,需要去队头的写入者将值写入 buffer,然后立即恢复该写入者和当次的读取者。
// 2、Channel 当中有挂起的写入者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者
// 3、Channel 的 buffer 为空,则读取者被存入挂起列表(reader_list)等待新的写入者写入时再恢复。
void try_push_reader(ReaderAwaiter<ValueType> *reader_awaiter)
{
std::unique_lock lock(_channel_lock);
check_closed();
// 1、buffer 非空,读取者从buffer当中读取值
if (!_buffer.empty())
{
auto value = _buffer.front();
_buffer.pop();
// 如果有等待写入的,唤醒等待写入队列的第一个写入者
if (!_writer_list.empty())
{
auto writer = _writer_list.front();
_writer_list.pop_front();
// 将值写入buffer
_buffer.push(writer->value());
lock.unlock();
writer->resume();
}
else
{
lock.unlock();
}
reader_awaiter->resume(value);
return;
}
// 2、有挂起的写入者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者
if (!_writer_list.empty())
{
auto writer = _writer_list.front();
_writer_list.pop_front();
lock.unlock();
reader_awaiter->resume(writer->value());
writer->resume();
return;
}
// 3、buffer 为空,则读取者被存入挂起列表(reader_list)等待新的写入者写入时再恢复。
_reader_list.push_back(reader_awaiter);
}
struct ChannelClosedException : public std::exception
{
const char *what() const noexcept override
{
return "Channel is closed.";
}
};
void check_closed()
{
// 如果关闭,则抛出异常
if (!_is_active.load(std::memory_order_relaxed))
{
throw ChannelClosedException();
}
}
explicit Channel(int capacity = 0)
: _buffer_capacity(capacity)
{
_is_active.store(true, std::memory_order_relaxed);
}
// true表示Channel尚未关闭
bool is_active()
{
return _is_active.load(std::memory_order_relaxed);
}
// 关闭Channel
void close()
{
bool expect = true;
// 判断如果已经关闭,则不再重复操作
// 比较 _is_active 为 true 时才会完成设置操作,并且返回 true
if (_is_active.compare_exchange_strong(expect, false, std::memory_order_relaxed))
{
// 清理资源
clean_up();
}
}
// 移除
void remove_reader(ReaderAwaiter<ValueType> *reader_awaiter)
{
std::lock_guard lock(_channel_lock);
_reader_list.remove(reader_awaiter);
}
void remove_writer(WriterAwaiter<ValueType> *writer_awaiter)
{
std::lock_guard lock(_channel_lock);
_writer_list.remove(writer_awaiter);
}
Channel(Channel &&) = delete;
Channel(Channel &) = delete;
Channel &operator=(Channel &) = delete;
~Channel()
{
close();
}
private:
void clean_up()
{
std::lock_guard lock(_channel_lock);
// 需要对已经挂起等待的协程予以恢复执行
for (auto writer : _writer_list)
{
writer->resume();
}
_writer_list.clear();
for (auto reader : _reader_list)
{
reader->resume();
}
_reader_list.clear();
// 清空buffer
decltype(_buffer)().swap(_buffer);
// //等价于
// decltype(_buffer) empty_buffer;
// _buffer.swap(empty_buffer);
}
private:
int _buffer_capacity; //_buffer容量
std::queue<ValueType> _buffer; //_buffer队列
std::list<WriterAwaiter<ValueType> *> _writer_list; // buffer已经满时,新来的写入者需要挂起保存在这里等待恢复
std::list<ReaderAwaiter<ValueType> *> _reader_list; // buffer已经空时,新来的读取者需要挂起保存在这里等待恢复
std::atomic<bool> _is_active; // Channel的状态标识
std::mutex _channel_lock; // Channel的互斥锁
std::condition_variable _channel_condition; // Channel的条件变量
};
完整代码和运行结果
cpp
//executor.hpp
#ifndef __EXECUTOR_HPP__
#define __EXECUTOR_HPP__
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
#include <optional>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <list>
#include <memory>
#include <chrono>
#include <thread>
#include <unistd.h>
#include <future>
#include <queue>
using namespace std;
//调度器抽象接口
class AbstractExecutor
{
public:
virtual void execute(std::function<void()> &&func) = 0;
};
// 无操作调度器,直接在当前线程执行
class NoopExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
func();
}
};
//创建新线程调度器,每次调度都创建一个新的线程,detach表示线程不受主线程控制,主线程可以继续执行
class NewThreadExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
std::thread(func).detach();
}
};
//异步调度器,每次调度都创建一个新的线程,async表示线程会在后台执行,主线程可以继续执行
//与NewThreadExecutor差不多,区别是调度时交给std::async处理,利用async背后的线程调度,提升线程的利用率
class AsyncExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
auto future = std::async(func);
}
};
//简单的单事件循环调度器
class LooperExecutor : public AbstractExecutor
{
private:
std::condition_variable _queue_condition;
std::mutex _queue_lock;
std::queue<std::function<void()>> _executable_queue;
//true为活动状态,false为停止状态
std::atomic<bool> _is_active;
//工作线程,用于处理事件循环
std::thread _work_thread;
//处理事件循环
void run_loop()
{
//检查当前事件循环是否处于工作状态,或者调度器队列没有清空
while(_is_active.load(std::memory_order_relaxed) || !_executable_queue.empty())
{
std::unique_lock lock(_queue_lock);
//如果队列为空,就需要等待新任务加入队列或者关闭事件循环的通知
if(_executable_queue.empty())
{
//队列为空,那么说明收到的是关闭的通知
_queue_condition.wait(lock);
//如果队列为空,那么说明收到的是关闭的通知
if(_executable_queue.empty())
{
//现有逻辑下此处break可以
//使用continue可以再次检查状态和队列,方便将来扩展
continue;
}
}
//取出任务,解锁,执行任务
auto func = _executable_queue.front();
_executable_queue.pop();
lock.unlock();
func();
}
}
public:
LooperExecutor()
{
_is_active.store(true, std::memory_order_relaxed);
_work_thread = std::thread(&LooperExecutor::run_loop, this);
}
~LooperExecutor()
{
shutdown(false);
//等待线程执行完,防止出现意外情况
join();
}
void execute(std::function<void()> &&func) override
{
std::unique_lock lock(_queue_lock);
if(_is_active.load(std::memory_order_relaxed))
{
_executable_queue.push(func);
lock.unlock();
//通知队列,主要用于队列之前为空时调用wait等待的情况
//通知不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_one();
}
}
//关闭事件循环,wait_for_complete表示是否等待所有任务执行完毕
void shutdown(bool wait_for_complete = true)
{
//修改后立即生效,在run_loop当中就能尽早(加锁前)就检测到_is_active的变化
_is_active.store(false, std::memory_order_relaxed);
if(!wait_for_complete)
{
std::unique_lock lock(_queue_lock);
//清空任务队列
decltype(_executable_queue) empty_queue;
std::swap(_executable_queue, empty_queue);
lock.unlock();
}
//通知wait函数,避免Looper线程不退出
//不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_all();
}
void join()
{
if(_work_thread.joinable())
{
_work_thread.join();
}
}
};
//共享事件循环调度器,使用单例模式,作用就是让各个协程共享一个LooperExecutor实例
//使用时直接调用execute方法即可
class SharedLooperExecutor : public AbstractExecutor
{
public:
void execute(std::function<void()> &&func) override
{
static LooperExecutor SharedLooperExecutor;
SharedLooperExecutor.execute(std::move(func));
}
};
//定时任务描述--包含一个函数和它要执行的绝对时间
class DelayedExecutable
{
public:
DelayedExecutable(std::function<void()> &&func, long long delay)
:_func(std::move(func))
{
using namespace std;
using namespace std::chrono;
auto now = system_clock::now();
//当前时间戳,单位毫秒
auto current = duration_cast<milliseconds>(now.time_since_epoch()).count();
//计算出任务的计划执行时间
_scheduled_time = current + delay;
}
//调用时,返回从当前时间还需要多少毫秒到任务执行时间
long long delay() const
{
using namespace std;
using namespace std::chrono;
auto now = system_clock::now();
auto current = duration_cast<milliseconds>(now.time_since_epoch()).count();
return _scheduled_time - current;
}
//返回执行时间
long long get_scheduled_time() const
{
return _scheduled_time;
}
//执行任务
void operator()()
{
_func();
}
private:
long long _scheduled_time;
std::function<void()> _func;
};
//DelayedExecutable比较仿函数
class DelayedExecutableCompare
{
public:
bool operator() (DelayedExecutable &left, DelayedExecutable &right)
{
//按照任务执行时间升序排列
return left.get_scheduled_time() > right.get_scheduled_time();
}
};
//定时任务调度器
class Scheduler
{
private:
std::condition_variable _queue_condition;
std::mutex _queue_lock;
//注意这里改用优先级队列--小顶堆--greater,所以DelayExecutableCompare要用>符号
std::priority_queue<DelayedExecutable, std::vector<DelayedExecutable>, DelayedExecutableCompare> _executable_queue;
std::atomic<bool> _is_active;
std::thread _work_thread;
void run_loop()
{
while(_is_active.load(std::memory_order_relaxed) || !_executable_queue.empty())
{
std::unique_lock lock(_queue_lock);
if(_executable_queue.empty())
{
_queue_condition.wait(lock);
if(_executable_queue.empty())
{
continue;
}
}
//需要判断最先执行的任务是否到时间了
auto executable = _executable_queue.top();
long long delay = executable.delay();
if(delay > 0)
{
//队头任务还没到时间,再等待delay毫秒
auto status = _queue_condition.wait_for(lock, std::chrono::milliseconds(delay));
//如果等待期间没有延时比 delay 更小的任务加入,这里就会返回 timeout
//如果有小于 delay 毫秒的任务加入,wait被唤醒,这里就会返回 not_timeout
if(status != std::cv_status::timeout)
{
// 不是 timeout,需要重新计算队头的延时
continue;
}
}
_executable_queue.pop();
lock.unlock();
executable();//执行任务--operator()
}
}
public:
Scheduler()
{
_is_active.store(true, std::memory_order_relaxed);
_work_thread = std::thread(&Scheduler::run_loop, this);
}
~Scheduler()
{
shutdown(false);
//等待线程执行完,防止出现意外情况
join();
}
void execute(std::function<void()> &&func, long long delay)
{
delay = delay < 0 ? 0 : delay;
std::unique_lock lock(_queue_lock);
if(_is_active.load(std::memory_order_relaxed))
{
// 只有队列为空或者比当前队头任务的延时更小时,需要调用 notify_one
// 其他情况只需要按顺序依次执行即可
bool need_notify = _executable_queue.empty() || _executable_queue.top().delay() > delay;
_executable_queue.push(DelayedExecutable(std::move(func), delay));
lock.unlock();
if (need_notify)
{
_queue_condition.notify_one();
}
}
}
//关闭事件循环,wait_for_complete表示是否等待所有任务执行完毕
void shutdown(bool wait_for_complete = true)
{
//修改后立即生效,在run_loop当中就能尽早(加锁前)就检测到_is_active的变化
_is_active.store(false, std::memory_order_relaxed);
if(!wait_for_complete)
{
std::unique_lock lock(_queue_lock);
//清空任务队列
decltype(_executable_queue) empty_queue;
std::swap(_executable_queue, empty_queue);
lock.unlock();
}
//通知wait函数,避免线程不退出
//不需要加锁,否则锁会交给wait方导致当前线程阻塞
_queue_condition.notify_all();
}
void join()
{
if(_work_thread.joinable())
{
_work_thread.join();
}
}
};
#endif
cpp
//task.cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <coroutine>
#include <exception>
#include <utility>
#include <optional>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <list>
#include <memory>
#include <chrono>
#include <thread>
#include <unistd.h>
#include "executor.hpp"
using namespace std;
template <typename T>
struct Result
{
// 初始化为默认值
explicit Result() = default;
// 当Task正常返回时用结果初始化Result
explicit Result(T &&value)
: _value(std::move(value))
{
}
// 当Task抛出异常时用异常初始化Result
explicit Result(std::exception_ptr &&exception_ptr)
: _exception_ptr(std::move(exception_ptr))
{
}
// 读取结果,有异常则抛出异常
T get_or_throw()
{
if (_exception_ptr)
{
// 重新抛出
std::rethrow_exception(_exception_ptr);
}
return _value;
}
private:
T _value;
std::exception_ptr _exception_ptr;
};
//Result的void特化
template<>
struct Result<void>
{
// 初始化为默认值
explicit Result() = default;
// 当Task抛出异常时用异常初始化Result
explicit Result(std::exception_ptr &&exception_ptr)
: _exception_ptr(std::move(exception_ptr))
{
}
// 读取结果,有异常则抛出异常
void get_or_throw()
{
if (_exception_ptr)
{
// 重新抛出
std::rethrow_exception(_exception_ptr);
}
}
private:
std::exception_ptr _exception_ptr;
};
//前置声明
template <typename ResultType, typename Executor>
struct Task;
template <typename Executor>
struct Task<void, Executor>;
// 调度器的类型有多种,因此专门提供一个模板参数 Executor
template <typename Result, typename Executor>
struct TaskAwaiter
{
explicit TaskAwaiter(AbstractExecutor *executor, Task<Result, Executor> &&task) noexcept
: _executor(executor), _task(std::move(task))
{
}
TaskAwaiter(TaskAwaiter &&completion) noexcept
: _executor(completion._executor), _task(std::exchange(completion._task, {}))
{
}
TaskAwaiter(TaskAwaiter &) = delete;
TaskAwaiter &operator=(TaskAwaiter &) = delete;
// constexpr的作用:
// 1. 可以在编译时计算出结果
// 2. 可以在编译时判断是否需要挂起
constexpr bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
// //_task执行完之后才调用resume,此时一定有值
// _task.finally([handle]()
// { handle.resume(); });
// 改为调度器调度
_task.finally([handle, this]()
{ _executor->execute([handle]()
{ handle.resume(); }); });
}
// 这里是co_await之后的返回值
Result await_resume() noexcept
{
return _task.get_result();
}
private:
Task<Result, Executor> _task;
AbstractExecutor *_executor;
};
struct DispatchAwaiter
{
explicit DispatchAwaiter(AbstractExecutor *executor) noexcept
: _executor(executor)
{
}
bool await_ready() const
{
return false;
}
void await_suspend(std::coroutine_handle<> handle) const
{
// 调度到协程对应的调度器上
_executor->execute([handle]()
{ handle.resume(); });
}
void await_resume() const
{
}
private:
AbstractExecutor *_executor;
};
struct SleepAwaiter
{
explicit SleepAwaiter(AbstractExecutor *executor, long long duration) noexcept
: _executor(executor), _duration(duration)
{
}
bool await_ready() const
{
return false;
}
void await_suspend(std::coroutine_handle<> handle)
{
// 自定义的定时任务调度器,全局只需要一个实例
static Scheduler scheduler;
scheduler.execute([this, handle]()
{
//_duration毫秒之后执行下面的代码
_executor->execute([handle]()
{
handle.resume();
});
}, _duration);
}
void await_resume()
{
}
private:
AbstractExecutor *_executor;
long long _duration; // 等待的时间,单位为毫秒
};
// 前置声明
template <typename ValueType>
struct WriterAwaiter;
template <typename ValueType>
struct ReaderAwaiter;
template <typename ValueType>
struct Channel
{
auto write(ValueType value)
{
check_closed();
return WriterAwaiter<ValueType>(this, value);
}
auto operator<<(ValueType value)
{
return write(value); // 复用
}
auto read()
{
check_closed();
return ReaderAwaiter<ValueType>(this);
}
auto operator>>(ValueType &value_ref)
{
auto awaiter = read();
// 保存待赋值的变量的地址,方便后续写入
awaiter.set_p_value(&value_ref);
return awaiter;
}
// try_push_writer 调用时,意味着有一个新的写入者挂起准备写入值到 Channel 当中,这时候有以下几种情况:
// 1、Channel 当中有挂起的读取者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者
// 2、Channel 的 buffer 没满,写入者把值写入 buffer,然后立即恢复执行。
// 3、Channel 的 buffer 已满,则写入者被存入挂起列表(writer_list)等待新的读取者读取时再恢复。
void try_push_writer(WriterAwaiter<ValueType> *writer_awaiter)
{
std::unique_lock lock(_channel_lock);
check_closed();
// 1、检查有没有挂起的读取者
if (!_reader_list.empty())
{
auto reader = _reader_list.front();
_reader_list.pop_front();
lock.unlock();
reader->resume(writer_awaiter->value());
writer_awaiter->resume();
return;
}
// 2、buffer 没满,写入buffer
if (_buffer.size() < _buffer_capacity)
{
_buffer.push(writer_awaiter->value());
lock.unlock();
writer_awaiter->resume();
return;
}
// 3、buffer 已满,写入者被存入挂起列表(writer_list)等待新的读取者读取时再恢复。
_writer_list.push_back(writer_awaiter);
}
// 相对应的,try_push_reader 调用时,意味着有一个新的读取者挂起准备从 Channel 当中读取值,这时候有以下几种情况:
// 1、Channel 的 buffer 非空,读取者从 buffer 当中读取值,如果此时有挂起的写入者,需要去队头的写入者将值写入 buffer,然后立即恢复该写入者和当次的读取者。
// 2、Channel 当中有挂起的写入者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者
// 3、Channel 的 buffer 为空,则读取者被存入挂起列表(reader_list)等待新的写入者写入时再恢复。
void try_push_reader(ReaderAwaiter<ValueType> *reader_awaiter)
{
std::unique_lock lock(_channel_lock);
check_closed();
// 1、buffer 非空,读取者从buffer当中读取值
if (!_buffer.empty())
{
auto value = _buffer.front();
_buffer.pop();
// 如果有等待写入的,唤醒等待写入队列的第一个写入者
if (!_writer_list.empty())
{
auto writer = _writer_list.front();
_writer_list.pop_front();
// 将值写入buffer
_buffer.push(writer->value());
lock.unlock();
writer->resume();
}
else
{
lock.unlock();
}
reader_awaiter->resume(value);
return;
}
// 2、有挂起的写入者,写入者直接将要写入的值传给读取者,恢复读取者,恢复写入者
if (!_writer_list.empty())
{
auto writer = _writer_list.front();
_writer_list.pop_front();
lock.unlock();
reader_awaiter->resume(writer->value());
writer->resume();
return;
}
// 3、buffer 为空,则读取者被存入挂起列表(reader_list)等待新的写入者写入时再恢复。
_reader_list.push_back(reader_awaiter);
}
struct ChannelClosedException : public std::exception
{
const char *what() const noexcept override
{
return "Channel is closed.";
}
};
void check_closed()
{
// 如果关闭,则抛出异常
if (!_is_active.load(std::memory_order_relaxed))
{
throw ChannelClosedException();
}
}
explicit Channel(int capacity = 0)
: _buffer_capacity(capacity)
{
_is_active.store(true, std::memory_order_relaxed);
}
// true表示Channel尚未关闭
bool is_active()
{
return _is_active.load(std::memory_order_relaxed);
}
// 关闭Channel
void close()
{
bool expect = true;
// 判断如果已经关闭,则不再重复操作
// 比较 _is_active 为 true 时才会完成设置操作,并且返回 true
if (_is_active.compare_exchange_strong(expect, false, std::memory_order_relaxed))
{
// 清理资源
clean_up();
}
}
// 移除
void remove_reader(ReaderAwaiter<ValueType> *reader_awaiter)
{
std::lock_guard lock(_channel_lock);
_reader_list.remove(reader_awaiter);
}
void remove_writer(WriterAwaiter<ValueType> *writer_awaiter)
{
std::lock_guard lock(_channel_lock);
_writer_list.remove(writer_awaiter);
}
Channel(Channel &&) = delete;
Channel(Channel &) = delete;
Channel &operator=(Channel &) = delete;
~Channel()
{
close();
}
private:
void clean_up()
{
std::lock_guard lock(_channel_lock);
// 需要对已经挂起等待的协程予以恢复执行
for (auto writer : _writer_list)
{
writer->resume();
}
_writer_list.clear();
for (auto reader : _reader_list)
{
reader->resume();
}
_reader_list.clear();
// 清空buffer
decltype(_buffer)().swap(_buffer);
// //等价于
// decltype(_buffer) empty_buffer;
// _buffer.swap(empty_buffer);
}
private:
int _buffer_capacity; //_buffer容量
std::queue<ValueType> _buffer; //_buffer队列
std::list<WriterAwaiter<ValueType> *> _writer_list; // buffer已经满时,新来的写入者需要挂起保存在这里等待恢复
std::list<ReaderAwaiter<ValueType> *> _reader_list; // buffer已经空时,新来的读取者需要挂起保存在这里等待恢复
std::atomic<bool> _is_active; // Channel的状态标识
std::mutex _channel_lock; // Channel的互斥锁
std::condition_variable _channel_condition; // Channel的条件变量
};
//前置声明
template <typename ValueType>
struct Channel;
template <typename ValueType>
struct WriterAwaiter
{
WriterAwaiter(Channel<ValueType> *channel, ValueType value)
: _channel(channel), _value(value)
{
}
WriterAwaiter(WriterAwaiter &&other) noexcept
: _channel(std::exchange(other._channel, nullptr)),
_executor(std::exchange(other._executor, nullptr)),
_value(other._value),
_handle(other._handle)
{
}
bool await_ready()
{
return false;
}
auto await_suspend(std::coroutine_handle<> coroutine_handle)
{
// 记录协程handle,恢复时用
_handle = coroutine_handle;
// 将自身传给Channel,Channel内部会根据自身状态处理是否立即恢复或者挂起
_channel->try_push_writer(this);
}
void await_resume()
{
// Channel 关闭时也会将挂起的读写协程恢复
// 要检查是否是关闭引起的恢复,如果是,check_closed 会抛出 Channel 关闭异常
_channel->check_closed();
_channel = nullptr;
}
// Channel当中恢复该协程时调用resume函数
void resume()
{
// 将调度器调度的逻辑封装在这里
if (_executor)
{
_executor->execute([this]()
{ _handle.resume(); });
}
else
{
_handle.resume();
}
}
ValueType value() const
{
return _value;
}
void set_executor(AbstractExecutor *executor)
{
if(executor)
{
_executor = executor;
}
}
~WriterAwaiter()
{
//_channel不为空,说明协程提前销毁了
// 调用channel的remove_reader将自己直接移除
if (_channel)
_channel->remove_writer(this);
}
private:
Channel<ValueType> *_channel;
AbstractExecutor *_executor = nullptr;
ValueType _value;
std::coroutine_handle<> _handle;
};
//前置声明
template <typename ValueType>
struct Channel;
template <typename ValueType>
struct ReaderAwaiter
{
explicit ReaderAwaiter(Channel<ValueType> *channel)
: _channel(channel)
{
}
// 实现移动构造函数,主要目的是原对象的channel置为空
ReaderAwaiter(ReaderAwaiter &&other) noexcept
: _channel(std::exchange(other._channel, nullptr)),
_executor(std::exchange(other._executor, nullptr)),
_value(other._value),
_p_value(std::exchange(other._p_value, nullptr)),
_handle(other._handle)
{
}
bool await_ready()
{
return false;
}
auto await_suspend(std::coroutine_handle<> coroutine_handle)
{
_handle = coroutine_handle;
// 将自身传给Channel,Channel内部会根据自身状态处理是否立即恢复或者挂起
_channel->try_push_reader(this);
}
int await_resume()
{
// Channel 关闭时也会将挂起的读写协程恢复
// 要检查是否是关闭引起的恢复,如果是,check_closed 会抛出 Channel 关闭异常
_channel->check_closed();
// 协程恢复,channel已经没用了,置空
_channel = nullptr;
// 读取到的值
return _value;
}
// Channel当中恢复该协程时调用resume函数
void resume(ValueType value)
{
_value = value;
if (_p_value)
{
*_p_value = value;
}
resume();
}
// Channel 关闭时调用 resume() 函数来恢复该协程
// 在 await_resume 当中,如果 Channel 关闭,会抛出 Channel 关闭异常
void resume()
{
if (_executor)
{
_executor->execute([this]()
{ _handle.resume(); });
}
else
{
_handle.resume();
}
}
void set_p_value(ValueType *p_value)
{
_p_value = p_value;
}
void set_executor(AbstractExecutor *executor)
{
if(executor)
{
_executor = executor;
}
}
~ReaderAwaiter()
{
//_channel不为空,说明协程提前销毁了
// 调用channel的remove_reader函数将自己直接移除
if (_channel)
_channel->remove_reader(this);
}
private:
Channel<ValueType> *_channel;
AbstractExecutor *_executor = nullptr;
ValueType _value;
// 用于 channel >> received; 这种情况
// 需要将变量的地址传入,协程恢复时写入变量内存
ValueType *_p_value = nullptr;
std::coroutine_handle<> _handle;
};
//前置声明
struct DispatchAwaiter;
template <typename ResultType, typename Executor>
struct Task;
template <typename Executor>
struct Task<void,Executor>;
struct SleepAwaiter;
template <typename Result, typename Executor>
struct TaskAwaiter;
template <typename ValueType>
struct ReaderAwaiter;
template <typename ValueType>
struct WriterAwaiter;
template <typename ResultType, typename Executor>
struct TaskPromise
{
// 协程启动时也需要在恢复时实现调度
DispatchAwaiter initial_suspend()
{
return DispatchAwaiter{&_executor};
}
std::suspend_always final_suspend() noexcept
{
return {};
}
Task<ResultType, Executor> get_return_object()
{
return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
template <typename _ResultType, typename _Executor>
TaskAwaiter<_ResultType, _Executor> await_transform(Task<_ResultType, _Executor> &&task)
{
return TaskAwaiter<_ResultType, _Executor>(&_executor, std::move(task));
}
template <typename _Rep, typename _Period>
SleepAwaiter await_transform(std::chrono::duration<_Rep, _Period> &&duration)
{
return SleepAwaiter(&_executor, std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}
template <typename _ValueType>
ReaderAwaiter<_ValueType> await_transform(ReaderAwaiter<_ValueType> reader_awaiter)
{
reader_awaiter.executor = &_executor;
return reader_awaiter;
}
template <typename _ValueType>
WriterAwaiter<_ValueType> await_transform(WriterAwaiter<_ValueType> writer_awaiter)
{
writer_awaiter.executor = &_executor;
return writer_awaiter;
}
void unhandled_exception()
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(std::current_exception()));
// 通知get_result当中的wait
_completion.notify_all();
// 调用回调
notify_callback();
}
void return_value(ResultType value)
{
std::lock_guard lock(_completion_lock);
_result = Result<ResultType>(std::move(value));
_completion.notify_all();
// 调用回调
notify_callback();
}
// 获取结果,若没有结果则阻塞等待--提供给Task使用
ResultType get_result()
{
// 如果_result没有值,则等待写入值之后调用notify_all
std::unique_lock lock(_completion_lock);
if (!_result.has_value())
{
// 等待写入值之后调用notify_all
_completion.wait(lock);
}
// 有值,则返回(或者抛出异常)
return _result->get_or_throw();
}
// 注册回调
void on_completed(std::function<void(Result<ResultType>)> &&func)
{
std::unique_lock lock(_completion_lock);
// 加锁判断result
if (_result.has_value())
{
//_result有值,则调用回调
auto value = _result.value();
// 解锁之后调用func
lock.unlock();
func(std::move(value));
}
else
{
// 否则添加回调函数,等待回调
_completion_callbacks.push_back(func);
}
}
private:
Executor _executor;
std::optional<Result<ResultType>> _result;
std::mutex _completion_lock;
std::condition_variable _completion;
// 回调列表,我们允许对同一个 Task 添加多个回调
std::list<std::function<void(Result<ResultType>)>> _completion_callbacks;
// 通知回调,调用回调函数,将结果传递给回调函数
void notify_callback()
{
auto value = _result.value();
for (auto &callback : _completion_callbacks)
{
callback(value);
}
// 调用完成,清空回调
_completion_callbacks.clear();
}
};
//TaskPromise的void特化
template <typename Executor>
struct TaskPromise<void, Executor>
{
// 协程启动时也需要在恢复时实现调度
DispatchAwaiter initial_suspend()
{
return DispatchAwaiter{&_executor};
}
std::suspend_always final_suspend() noexcept
{
return {};
}
Task<void, Executor> get_return_object()
{
return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
}
template <typename _ResultType, typename _Executor>
TaskAwaiter<_ResultType, _Executor> await_transform(Task<_ResultType, _Executor> &&task)
{
return TaskAwaiter<_ResultType, _Executor>(&_executor, std::move(task));
}
template <typename _Rep, typename _Period>
SleepAwaiter await_transform(std::chrono::duration<_Rep, _Period> &&duration)
{
return SleepAwaiter(&_executor, std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}
template <typename _ValueType>
ReaderAwaiter<_ValueType> await_transform(ReaderAwaiter<_ValueType> reader_awaiter)
{
reader_awaiter.set_executor(&_executor);
return reader_awaiter;
}
template <typename _ValueType>
WriterAwaiter<_ValueType> await_transform(WriterAwaiter<_ValueType> writer_awaiter)
{
writer_awaiter.set_executor(&_executor);
return writer_awaiter;
}
void unhandled_exception()
{
std::lock_guard lock(_completion_lock);
_result = Result<void>(std::move(std::current_exception()));
// 通知get_result当中的wait
_completion.notify_all();
// 调用回调
notify_callback();
}
// void return_value(ResultType value)
// {
// std::lock_guard lock(_completion_lock);
// _result = Result<ResultType>(std::move(value));
// _completion.notify_all();
// // 调用回调
// notify_callback();
// }
void return_void()
{
std::lock_guard lock(_completion_lock);
_result = Result<void>();
_completion.notify_all();
notify_callback();
}
// 获取结果,若没有结果则阻塞等待--提供给Task使用
void get_result()
{
// 如果_result没有值,则等待写入值之后调用notify_all
std::unique_lock lock(_completion_lock);
if (!_result.has_value())
{
// 等待写入值之后调用notify_all
_completion.wait(lock);
}
// 有值,则返回(或者抛出异常)
_result->get_or_throw();
}
// 注册回调
void on_completed(std::function<void(Result<void>)> &&func)
{
std::unique_lock lock(_completion_lock);
// 加锁判断result
if (_result.has_value())
{
//_result有值,则调用回调
auto value = _result.value();
// 解锁之后调用func
lock.unlock();
func(std::move(value));
}
else
{
// 否则添加回调函数,等待回调
_completion_callbacks.push_back(func);
}
}
private:
Executor _executor;
std::optional<Result<void>> _result;
std::mutex _completion_lock;
std::condition_variable _completion;
// 回调列表,我们允许对同一个 Task 添加多个回调
std::list<std::function<void(Result<void>)>> _completion_callbacks;
// 通知回调,调用回调函数,将结果传递给回调函数
void notify_callback()
{
auto value = _result.value();
for (auto &callback : _completion_callbacks)
{
callback(value);
}
// 调用完成,清空回调
_completion_callbacks.clear();
}
};
//前置声明
template <typename ResultType, typename Executor>
struct TaskPromise;
template <typename Executor>
struct TaskPromise<void, Executor>;
// NewThreadExecutor 是 AbstractExecutor 的子类,作为模板参数 Executor 的默认值
template <typename ResultType, typename Executor>
struct Task
{
using promise_type = TaskPromise<ResultType, Executor>;
ResultType get_result()
{
return _handle.promise().get_result();
}
// 如果有值,处理值
Task &then(std::function<void(ResultType)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
func(result.get_or_throw());
}
catch (std::exception &e)
{
// 忽略异常
} });
return *this;
}
// 如果有异常,处理异常
Task &catching(std::function<void(std::exception &)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
// 忽略返回值
result.get_or_throw();
}
catch (std::exception &e)
{
func(e);
} });
return *this;
}
Task &finally(std::function<void()> &&func)
{
_handle.promise().on_completed([func](auto result)
{ func(); });
return *this;
}
explicit Task(std::coroutine_handle<promise_type> handle) noexcept
: _handle(handle)
{
}
Task(Task &&task) noexcept
: _handle(std::exchange(task._handle, {}))
{
}
Task(Task &) = delete;
Task &operator=(Task &) = delete;
~Task()
{
if (_handle)
{
_handle.destroy();
}
}
private:
std::coroutine_handle<TaskPromise<ResultType, Executor>> _handle;
};
//Task的void特化
template <typename Executor>
struct Task<void, Executor>
{
using promise_type = TaskPromise<void, Executor>;
void get_result()
{
return _handle.promise().get_result();
}
// 如果有值,处理值
Task &then(std::function<void()> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
result.get_or_throw();
func();
}
catch (std::exception &e)
{
// 忽略异常
} });
return *this;
}
// 如果有异常,处理异常
Task &catching(std::function<void(std::exception &)> &&func)
{
_handle.promise().on_completed([func](auto result)
{
try
{
// 忽略返回值
result.get_or_throw();
}
catch (std::exception &e)
{
func(e);
} });
return *this;
}
Task &finally(std::function<void()> &&func)
{
_handle.promise().on_completed([func](auto result)
{ func(); });
return *this;
}
explicit Task(std::coroutine_handle<promise_type> handle) noexcept
: _handle(handle)
{
}
Task(Task &&task) noexcept
: _handle(std::exchange(task._handle, {}))
{
}
Task(Task &) = delete;
Task &operator=(Task &) = delete;
~Task()
{
if (_handle)
{
_handle.destroy();
}
}
private:
std::coroutine_handle<TaskPromise<void, Executor>> _handle;
};
using namespace std::chrono_literals;
Task<void, LooperExecutor> Producer(Channel<int> &channel)
{
int i = 0;
while (i < 10)
{
std::cout << "send: " << i << std::endl;
// 或者使用 write 函数:co_await channel.write(i++);
co_await (channel << i++);
co_await 300ms;
}
co_await 4000ms;
channel.close();
std::cout << "close channel, exit." << std::endl;
}
Task<void, LooperExecutor> Consumer(Channel<int> &channel)
{
while (channel.is_active())
{
try
{
// 或者使用 read 函数:auto received = co_await channel.read();
int received;
co_await (channel >> received);
std::cout << "receive: " << received << std::endl;
co_await 2s;
}
catch (std::exception &e)
{
std::cout << "exception: " << e.what() << std::endl;
}
}
std::cout << "exit." << std::endl;
}
Task<void, LooperExecutor> Consumer2(Channel<int> &channel)
{
while (channel.is_active())
{
try
{
auto received = co_await channel.read();
std::cout << "receive2: " << received << std::endl;
co_await 3s;
}
catch (std::exception &e)
{
std::cout << "exception2: " << e.what() << std::endl;
}
}
std::cout << "exit2." << std::endl;
}
void testChannel()
{
auto channel = Channel<int>(2);
auto producer = Producer(channel);
auto consumer = Consumer(channel);
auto consumer2 = Consumer2(channel);
// 等待协程执行完成再退出
producer.get_result();
consumer.get_result();
consumer2.get_result();
}
int main()
{
testChannel();
return 0;
}
运行结果:
bash
ubuntu@EVA:~/myfile/test$ g++ -o task task.cpp -std=c++20
ubuntu@EVA:~/myfile/test$ ./task
send: 0
receive: 0
send: 1
receive2: 1
send: 2
send: 3
send: 4
receive: 2
send: 5
receive2: 3
send: 6
receive: 4
send: 7
receive: 5
receive2: 6
send: 8
send: 9
receive: 7
receive2: 8
receive: 9
close channel, exit.
exception: Channel is closed.
exit.
exception2: Channel is closed.
exit2.
可以看出,C++协程的基础API的设计足够灵活,能够支撑非常复杂的需求场景。