在C++中,协程就是一个可以暂停和恢复的函数。
包含co_wait
、co_yield
、co_return
关键字的都可以叫协程。
看一个例子:
MyCoroGenerator<int> testFunc(int n)
{
std::cout << "Begin testFunc" << std::endl;
for (int i = 0; i < n; ++i) {
std::cout << "TestFunc before yield " << i << std::endl;
co_yield i;
std::cout << "TestFunc after yield " << i << std::endl;
}
std::cout << "End testFunc" << std::endl;
}
int main()
{
int inp = 10;
std::cout << "Before testFunc" << std::endl;
MyCoroGenerator<int> gen = testFunc(inp);
std::cout << "After testFunc" << std::endl;
for (int i = 0; i < inp; ++i) {
std::cout << "Cur input: " << i << std::endl;
std::cout << "Output value: " << gen.next() << std::endl;
std::cout << "After input: " << i << std::endl;
}
}
上面这段代码的执行结果是:
Before testFunc
After testFunc
Cur input: 0
Output value: Begin testFunc
TestFunc before yield 0
0
After input: 0
Cur input: 1
Output value: TestFunc after yield 0
TestFunc before yield 1
1
After input: 1
Cur input: 2
Output value: TestFunc after yield 1
TestFunc before yield 2
2
After input: 2
Cur input: 3
Output value: TestFunc after yield 2
TestFunc before yield 3
3
After input: 3
Cur input: 4
Output value: TestFunc after yield 3
TestFunc before yield 4
4
After input: 4
Cur input: 5
Output value: TestFunc after yield 4
TestFunc before yield 5
5
After input: 5
...
调用时发现,函数并没有一开始就执行,而是等到next
时才开始执行。执行到co_yield
就会自动暂停,下次next
才会继续执行
另外,函数本身并没有返回什么,但是这里可以使用MyCoroGenerator
接收。函数的控制权也转移到了MyCoroGenerator
,需要执行时就调用next
。
函数在暂停后执行,依然可以继续上次的状态,这是因为,协程在挂起期间,其上下文全部都被保留,下次执行时恢复。
std::coroutine_handle
类模板是实现协程的最底层的工具,负责存储协程的句柄。他可以特化为std::coroutine_handle
或者std::coroutine_handle
。
这里面的Promise
是实现协程必要的Promise
类,而且其名字必须是promise_type
。
协程会暂停,其上下文也会保留,std::coroutine_handle
就是保存和管理协程的地方。
std::coroutine_handle
中方法不多,但是每个都很重要,其中:
done
方法,可以查询协程是否已经结束resume
,恢复一个协程的执行destroy
,销毁一个协程std::coroutine_handle
是一个很底层的东西,没有RAII包裹,就像一个裸指针那样,需要手动创建、手动销毁。
所以需要一个包裹类,负责处理协程句柄的初始化和销毁,也就是Generator
C++的协程要求Generator必须有promise_type
这个类,名字也必须这个,不能是其他的名字,直接定义在Generator中最方便。
template <typename T>
struct MyCoroGenerator {
struct promise_type {
// todo
};
};
promise_type
中有一些固定接口,这些接口如果不实现,那么协程是不完整的。
initial_suspend
,协程是否在初始化结束后挂起,返回std::suspend_always、std::suspend_never,这是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起final_suspend
,协程最后一次执行是否挂起,返回值和上面函数相同。由于final_suspend是收尾阶段的工作,所以必须是noexceptunhandled_exception
,处理协程中未被处理的异常get_return_object
,返回一个Generator对象yield_value
,处理协程返回值,就是co_yield
传递过来的值return_void
,处理协程结束后的返回值,和return_value
同时只能存在一个,如果没有返回值就用return_void
return_value
,处理协程结束后返回值,和return_void
同时只能存在一个,如果有返回值就用return_value
解释一下yield_value
函数,co_yield
实际是一个语法糖,相当于co_wait promise.yield_value(i)
,只有实现了该函数,Genreator才能接收co_yield
的返回值
函数的返回值,需要回答协程要不要挂起,也就是std::suspend_always
或者std::suspend_never
,因为需要yield
后挂起,所以返回std::suspend_always
。
为了接收返回值,需要用转发和std::optional
,接收并存储返回值。
get_return_object
负责创建一个Generator,一般来说,使用std::coroutine_handle
接口从一个Promise创建句柄,然后使用这个句柄创建Generator。
为了配合创建函数,MyGenerator需要实现一个接收句柄的构造函数。也就是MyCoroGenerator(std::coroutine_handle
#include
#include
#include
template <typename T>
struct MyCoroGenerator {
/**
* @brief C++协程要求Generator必须有promise_type这个类型,名字也必须是promise_type
* 最方便的方法就是定义在Generator类内部
*
*/
struct promise_type {
/**
* @brief 存放协程返回值
* 由Generator从获取并返回
*/
std::optional<T> opt;
/**
* @brief 协程是否创建时就被挂起,函数名字也必须是initial_suspend
* std::suspend_always、std::suspend_never是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
*
* @return std::suspend_always 协程创建即挂起
*/
std::suspend_always initial_suspend() const
{
return {};
}
/**
* @brief 协程最后一次执行是否挂起,函数名字也必须是final_suspend
* 由于final_suspend是收尾阶段的工作,所以必须是noexcept
*
* @return std::suspend_always 协程最后一次执行也被挂起
*/
std::suspend_always final_suspend() const noexcept
{
return {};
}
/**
* @brief 处理协程中未捕获异常,函数名字必须是unhandled_exception
*
*/
void unhandled_exception()
{
std::exit(EXIT_FAILURE);
}
/**
* @brief 获取一个Generator对象,该对象从promise_type构造
*
* @return MyCoroGenerator
*/
MyCoroGenerator get_return_object()
{
return MyCoroGenerator { std::coroutine_handle<promise_type>::from_promise(*this) };
}
/**
* @brief 定制yield_value接口,接收co_yield返回的值
*
* @tparam Arg 值的类型
* @param arg co_yield返回值
* @return std::suspend_always 执行完后继续挂起
*/
template <typename Arg>
std::suspend_always yield_value(Arg&& arg)
{
opt.emplace(std::forward<Arg>(arg));
return {};
}
/**
* @brief 当协程结束co_return且没有返回值时,调用该函数
* 还有一个return_value(expr)函数,负责处理协程结束且有返回值的情况
*/
void return_void()
{
}
};
/**
* @brief 协程句柄,存储了协程上下文,一个非常底层的东西,没有RAII
* 用MyCoroGenerator包裹
*/
std::coroutine_handle<promise_type> handle;
/**
* @brief 默认构造函数
*
*/
MyCoroGenerator() = default;
/**
* @brief 通过一个handle构造一个Generator
*
* @param h 从promise_type构造出来的协程句柄
*/
MyCoroGenerator(std::coroutine_handle<promise_type> h)
: handle(h)
{
}
/**
* @brief 移动构造函数
*
* @param other 其他Generator对象
*/
MyCoroGenerator(MyCoroGenerator&& other)
{
if (handle) {
handle.destroy();
}
handle = other.handle;
other.handle = nullptr;
}
/**
* @brief 析构函数
*
*/
~MyCoroGenerator()
{
if (handle) {
handle.destroy();
}
}
/**
* @brief 移动赋值函数
*
* @param other 其他Generator对象
* @return MyCoroGenerator& 当前镀锡
*/
MyCoroGenerator& operator=(MyCoroGenerator&& other)
{
if (handle) {
handle.destroy();
}
handle = other.handle;
other.handle = nullptr;
return *this;
}
/**
* @brief 继续执行协程,并返回执行结果
*
* @return T& 返回的值
*/
T& next()
{
handle.resume();
if (handle.done()) {
// throw geneator_done("Generator done");
throw "Generator Error";
}
return *(handle.promise().opt);
}
private:
MyCoroGenerator(const MyCoroGenerator&) = delete;
MyCoroGenerator& operator=(const MyCoroGenerator&) = delete;
};
#include
#include
#include
template <typename T>
struct MyCoroGenerator {
/**
* @brief C++协程要求Generator必须有promise_type这个类型,名字也必须是promise_type
* 最方便的方法就是定义在Generator类内部
*
*/
struct promise_type {
/**
* @brief 存放协程返回值
* 由Generator从获取并返回
*/
std::optional<T> opt;
/**
* @brief 协程是否创建时就被挂起,函数名字也必须是initial_suspend
* std::suspend_always、std::suspend_never是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
*
* @return std::suspend_always 协程创建即挂起
*/
std::suspend_always initial_suspend() const
{
return {};
}
/**
* @brief 协程最后一次执行是否挂起,函数名字也必须是final_suspend
* 由于final_suspend是收尾阶段的工作,所以必须是noexcept
*
* @return std::suspend_always 协程最后一次执行也被挂起
*/
std::suspend_always final_suspend() const noexcept
{
return {};
}
/**
* @brief 处理协程中未捕获异常,函数名字必须是unhandled_exception
*
*/
void unhandled_exception()
{
std::exit(EXIT_FAILURE);
}
/**
* @brief 获取一个Generator对象,该对象从promise_type构造
*
* @return MyCoroGenerator
*/
MyCoroGenerator get_return_object()
{
return MyCoroGenerator { std::coroutine_handle<promise_type>::from_promise(*this) };
}
/**
* @brief 定制yield_value接口,接收co_yield返回的值
*
* @tparam Arg 值的类型
* @param arg co_yield返回值
* @return std::suspend_always 执行完后继续挂起
*/
template <typename Arg>
std::suspend_always yield_value(Arg&& arg)
{
opt.emplace(std::forward<Arg>(arg));
return {};
}
/**
* @brief 当协程结束co_return且没有返回值时,调用该函数
* 还有一个return_value(expr)函数,负责处理协程结束且有返回值的情况
*/
void return_void()
{
}
};
/**
* @brief 协程句柄,存储了协程上下文,一个非常底层的东西,没有RAII
* 用MyCoroGenerator包裹
*/
std::coroutine_handle<promise_type> handle;
/**
* @brief 默认构造函数
*
*/
MyCoroGenerator() = default;
/**
* @brief 通过一个handle构造一个Generator
*
* @param h 从promise_type构造出来的协程句柄
*/
MyCoroGenerator(std::coroutine_handle<promise_type> h)
: handle(h)
{
}
/**
* @brief 移动构造函数
*
* @param other 其他Generator对象
*/
MyCoroGenerator(MyCoroGenerator&& other)
{
if (handle) {
handle.destroy();
}
handle = other.handle;
other.handle = nullptr;
}
/**
* @brief 析构函数
*
*/
~MyCoroGenerator()
{
if (handle) {
handle.destroy();
}
}
/**
* @brief 移动赋值函数
*
* @param other 其他Generator对象
* @return MyCoroGenerator& 当前镀锡
*/
MyCoroGenerator& operator=(MyCoroGenerator&& other)
{
if (handle) {
handle.destroy();
}
handle = other.handle;
other.handle = nullptr;
return *this;
}
/**
* @brief 继续执行协程,并返回执行结果
*
* @return T& 返回的值
*/
T& next()
{
handle.resume();
if (handle.done()) {
// throw geneator_done("Generator done");
throw "Generator Error";
}
return *(handle.promise().opt);
}
private:
MyCoroGenerator(const MyCoroGenerator&) = delete;
MyCoroGenerator& operator=(const MyCoroGenerator&) = delete;
};
MyCoroGenerator<int> testFunc(int n)
{
std::cout << "Begin testFunc" << std::endl;
for (int i = 0; i < n; ++i) {
std::cout << "TestFunc before yield " << i << std::endl;
co_yield i;
std::cout << "TestFunc after yield " << i << std::endl;
}
std::cout << "End testFunc" << std::endl;
}
int main()
{
int inp = 10;
std::cout << "Before testFunc" << std::endl;
MyCoroGenerator<int> gen = testFunc(inp);
std::cout << "After testFunc" << std::endl;
for (int i = 0; i < inp; ++i) {
std::cout << "Cur input: " << i << std::endl;
std::cout << "Output value: " << gen.next() << std::endl;
std::cout << "After input: " << i << std::endl;
}
}
参考:C++ coroutine generator 实现笔记