点击上方蓝字 江湖评谈设为关注/星标
前言
相较于其它编程语言,比如Python/Ruby/Swift等等,协程的概念早早引入了。千呼万唤,C++20也终于引入了协程,但是作为基础性的语言,C++的协程实现代码过于庞杂,处于有而难用的情况。本篇看下C++20的协程。
C++20协程
协程的概念
我们经常接触到进程,线程。线程是进程中的一个执行单元,而协程呢?可以认为是比线程更小的执行单元,且协程可以分割控制函数和代码片段,意即它可以控制代码的细碎的点。比如代码初始化的时候,可以让协程控制其初始化的逻辑,代码挂起的时候,可以控制其代码挂起的逻辑等。
C++20的协程通过promise_type来定义和管理协程的生命周期。比如挂起,恢复,异常处理,返回值等操作。通过co_await
、co_return
和 co_yield来控制协程,通过以下函数进行实际的运作:
get_return_object(),返回协程的最终结果对象。它通过std::coroutine_handle将promise_type转化为协程对象,通常在返回值时使用。
initial_suspend(),定义协程开始时是否挂起。返回类型为std::suspend_always 时,协程会在开始时挂起;返回std::suspend_never 时,协程开始时不挂起。
final_suspend(),定义协程结束时是否挂起。通常用于决定协程执行完后是否等待其清理工作。
return_void(),协程正常结束时,调用此方法,表示没有返回值(即返回void)
unhandled_exception(),当协程中抛出异常时,会调用此函数。它的主要作用是处理协程中的未处理异常,通常可以选择终止程序或重新抛出异常。
例子1:控制函数
下面用个例子演示下这些概念。
//filename:xiecheng.cpp
#include <iostream>
#include <coroutine>
// 自定义协程类型
struct Example {
struct promise_type {
Example get_return_object() { return Example{ std::coroutine_handle<promise_type>::from_promise(*this) }; }
std::suspend_always initial_suspend() {
std::cout << "initial_suspend: Coroutine is suspended at the start.\n";
return {};
}
std::suspend_always final_suspend() noexcept {
std::cout << "final_suspend: Coroutine is suspended at the end.\n";
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle;
explicit Example(std::coroutine_handle<promise_type> h) : handle(h) {}
~Example() { if (handle) handle.destroy(); }
void resume() { handle.resume(); }
bool done() const { return handle.done(); }
};
// 协程函数
Example coroutine_function() {
std::cout << "Coroutine body execution begins.\n";
co_await std::suspend_always{};
std::cout << "Coroutine body execution resumes.\n";
}
int main() {
std::cout << "Main function starts.\n";
auto coro = coroutine_function();
// 协程的初始挂起状态
std::cout << "Resuming coroutine...\n";
coro.resume();
// 协程的最终挂起状态
std::cout << "Finalizing coroutine...\n";
coro.resume();
std::cout << "Main function ends.\n";
getchar();
return 0;
}
C++20中以上代码运行出了以下结果:
#g++ -std=c++23 xiecheng.cpp -o xiecheng
Main function starts.
initial_suspend: Coroutine is suspended at the start.
Resuming coroutine...
Coroutine body execution begins.
Finalizing coroutine...
Coroutine body execution resumes.
final_suspend: Coroutine is suspended at the end.
Main function ends.
通过结果,我们可以看到协程严密控制函数的运行。当我们调用coroutine_function函数的时候,首先会被运行的是initial_suspend函数,它的返回值std::suspend_always告诉coroutine_function函数。coroutine_function初始化的时候会被暂停。
后面调用coro.resume的时候恢复被挂起的函数coroutine_function。让这个函数进行运行打印出了:Coroutine body execution begins.。而在此之前coroutine_function完全没有被运行到,只是被初始化了。
下面在coroutine_function函数里面通过代码:co_await std::suspend_always{};又挂起了coroutine_function函数。通过Main函数里面的coro.resume();进行了恢复运行。
从上例子可以看到,协程可以控制函数的初始化,运行过程,以及运行的结果等等。
例子2:值返回
1.co_yield,一次返回一个值。2.co_return返回最终的值。其中的co_return可以作为协程promise_type结构函数return_value和return_void的返回值。
//filename:xiecheng.cpp
#include <iostream>
#include <coroutine>
#include <vector>
// 定义一个协程返回类型
struct generator {
struct promise_type {
int current_value;
generator get_return_object() {
return generator{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
// 初始挂起
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {
std::cout << "Coroutine has finished executing.\n";
}
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
void unhandled_exception() {
std::terminate();
}
};
std::coroutine_handle<promise_type> handle;
explicit generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~generator() {
if (handle) handle.destroy();
}
bool move_next() {
handle.resume();
return !handle.done();
}
// 获取当前值
int current_value() const {
return handle.promise().current_value;
}
};
generator count_up_to(int max) {
for (int i = 1; i <= max; ++i) {
co_yield i; // 每次返回一个值
}
//co_return 100;
co_return; //返回
}
int main() {
auto gen = count_up_to(5);
while (gen.move_next()) {
std::cout << gen.current_value() << " ";
}
std::cout << "\n";
return 0;
}
return_void函数表示协程结束了,可以通过co_return来调用(也可以不调用)。co_yield表示每次返回一个值。最后通过调用final_suspend函数表示协程结束了。
# g++ -std=c++23 xiecheng.cpp -o xiecheng
1 2 3 4 5 Coroutine has finished executing.
结尾
C++的协程总体来说不是太好控制,且代码过于繁多,对于一些新手理解比较复杂。参考Python协程就清爽了许多,如下:
import asyncio
# 定义协程函数
async def say_hello():
print("Hello")
await asyncio.sleep(1) # 模拟异步操作
print("World")
# 运行协程
async def main():
await say_hello()
# 运行事件循环
asyncio.run(main())
对于实际上的应用,依然有段距离。所以基本上都是第三方库提供支撑,比如cppcoro,Boost.Coroutine等。
往期精彩回顾
C++20 重量级的特性
C++安全指针,Rust用处何在?
关注公众号↑↑↑:江湖评谈