yoy
大纲链接:
Star_Tool:一个C++跨Windows和Linux的工具库_菜鸟小田的博客-CSDN博客
由于C++20中推出了协程,加上当前CPU频率的日新月异,协程在未来高性能计算上必然占有一席之地,本次在tool中添加了协程句柄的直接使用方法。
C++的协程主要围绕着co_await、co_yield、co_return 这三个关键字进行的,然后通过返回的句柄的形式找到对应的名字的类进行协程的挂起、临时返回、结束协程的三个操作。
后续C++协程的具体原理估计会再写一篇文章详细介绍,本章详细介绍我的协程句柄的使用方案。
由于协程是否挂起、协程是否再co_return 上有返回值等操作是根据协程类实现的具体形式而定的,故我的协程句柄的编写采用模板类的形式。
首先拥有两个不可创建式对象的句柄:
/* 有返回值标志位,用于填入协程句柄的模板参数 */
class has_return { has_return() = delete; };
/* 开始时挂起协程的标志位,用于填入协程句柄的模板参数 */
class suspend { suspend() = delete; };
这是两个不可创建式类,具体作用是作为模板参数填入co_handle的模板参数使之发挥不同的作用,顾名思义,has_return的功能时让协程调用co_return的时候有返回时,suspend的功能是协程被创建时处于挂起状态,需要手动进行开启。
接下来详细介绍co_handle这一重头戏。以下是co_handle的对外暴露的接口和类
template<typename ValueType,typename... Args>
class co_handle {
public:
/*
* 对外公布的数据结构
*/
/* co_yield、co_wait、co_return 处理数据结构*/
class promise_type;
class iterator;
public:
co_handle(promise_type* coroutine_promise_ptr);
iterator begin();
nullptr_t end();
iterator resume();
bool isEnd();
iterator getIterator();
};
promise_type:由于C++的协程co_await、co_yield、co_return这三个关键字是通过鸭子类型的形式找到句柄内的同名内嵌类promise_type(这个名字是必须且必须公开化,否则无法通过关键字调用到协程的相关方法),故该类暴露在public主要给协程库使用而非用户,故用户最好不要直接操作这个类或者创建这个类的对象,否则会造成不可预料的后果。
iterator:这是我给协程句柄创建的迭代器类,可以通过迭代器形式访问协程的返回值的操作。该迭代器的对象可以通过协程句柄的begin()、resume()方法获得或者可以通过直接构造以协程句柄地址的为参数直接构造相关协程句柄的迭代器。,迭代器的其他功能会在后续继续介绍。(注意:该迭代器属于单项迭代器,只能单向前进,即使重新获得begin()也是获得当前位置,因为协程是不可回退的)
co_handle(promise_type* coroutine_promise_ptr);
该方法也是为了配合co_await、co_yield、co_return关键字的使用而暴露在外的,不建议用户手动调用该方法,否则会造成不可预料的后果。
iterator begin();
nullptr_t end();
iterator resume();
iterator getIterator();
上述三个方法可以获得句柄的迭代器,同时配合遍历的for(auto iter: co_handle)的方式进行使用。其中getIterator()和begin()的方法区别在于,如果该协程的类型属于开始时挂起的类型,那么调用begin()会将协程状态从挂起变为运行,然后返回运行后的迭代器,而getIterator()方法不会操作协程的状态而直接获取迭代器。若是协程句柄类型本身属于开始时运行,则二者的结果没有任何区别。resume()方法如果不关注其返回值的话,直接使用代表着让一个挂起的协程继续运行。
bool isEnd();
上述方法判断协程句柄的任务是否已经结束,若协程任务结束则返回false,否则返回true。
好了,上述就介绍好了协程句柄的基本功能了,如果对协程没有其他需求例如开始时需要挂起、co_return需要有返回值,没有上面这两个需求,那么上述内容就够用了,那么我们来畅快地使用一次协程吧:
#include"tool/co_handle.h"
star::co_handle<int> funcMachine() {
//每次返回一个值
for (int i = 0; i < 10; i++)
co_yield i;
//稍微调皮一下,我们在这个地方加入了一个co_await,也就是说,我将在这里挂起这个协程而不返回数值。
co_await star::awaiter();
//规范起见,由于我的co_handle默认的是不会设置返回值的,故直接return;
co_return;
//当然,上述的co_return 是可有可无的,因为只要协程函数执行完毕,就会自动调用co_return;
}
上面就是我们的一个协程状态机,每一次循环都会返回i值并将协程挂起,那么我们在输出的时候就会得到结果:0 1 2 3 4 5 6 7 8 9 。还有一个问题,co_await 是否会返回数值呢? 这个结果我们最后再观察。接下来我们运行这个状态机:
有两种方式运行,一种是迭代器方式运行,一种是手动判断协程是否结束并使用resume函数继续协程。当然也支持传统迭代器的方式运行,因为重载了++运算符和begin()、end()我们可以通过for loop迭代的方式遍历:
迭代器for loop方式运行:
#include <iostream>
using namespace std;
int main()
{
auto suspendInstance = funcMachine();
for(auto& i : suspendInstance)
cout << i << endl;
}
运行结果:
0
1
2
3
4
5
6
7
8
9
9
注意到最后9输出了两次,说明使用co_await挂起线程时不会引起迭代器发生改变但会使得协程挂起。
使用resume运行:
#include <iostream>
using namespace std;
int main()
{
auto suspendInstance = funcMachine();
while(!suspendInstance.isEnd())
{
cout << *suspendInstance.resume() << endl;
}
}
运行结果:
1
2
3
4
5
6
7
8
9
9
9
注意到最后的9运行了三次,刚才两次还能解释,那么三次该怎么解释呢?
原因就在于在最后一次co_await挂起协程后,由于协程的运行还是没有结束的,其isEnd()还是保持着返回false,那么会再次循环输出当前迭代器内保存的返回值。
好的本节内容先到这里,由于篇幅有限,下一篇再写进阶用法,协程开始时挂起和具有返回值的协程。