C++20 协程体验
1 介绍
-
协程是比线程更加轻量级并发编程方式,CPU资源在用户态进行切换,CPU切换信息在用户态保存。
-
协程完成异步的调用流程,并对用户展示出同步的使用方式。
-
协程的调度由应用层决定,所以不同的实现会有不同的调度方式,调度策略比较灵活。
-
协程是基于线程之上运行,同一个线程中,协程是串行的,不会产生线程资源的竞争,不同的协程间却是相互交叉运行的,只要依赖的线程没有终止,协程最终会跳转回来。
-
协程可以充分利用单核CPU的资源,但是不太好利用多核CPU资源。
-
c++20 协程使用三大关键字 co_wait,co_return,co_yield
-
在函数中使用到以上关键字的函数被称为协程函数,并且通过该关键字完成跳转。
2 使用
如果要使用协程函数,需要定义promise_type以及基本成员函数实现。
包括get_return_object、initial_suspend、final_suspend、unhandled_exception。

演示代码最下面展示
co_return 执行完协程函数并返回结果
需要额外定义return_void函数。
流程分析:
1 可以看出调用co_return跳转到return_void,return_void执行完后,main函数向下执行。
2 "co_test1 end"并没有打印,说明协程函数co_test1分割开来,通过co_return切换了CPU资源,使主线程继续执行。



co_await 执行到异步操作处,判断并进行挂起操作。
使用co_await 还需要再定义xxxx类并实现await_ready、await_suspend、await_resume函数。
1 协程函数中调用co_await后,跳转xxx的await_ready并判断是否就绪,如果是true,则协程函数调回继续运行,反之进入await_suspend挂起,协程函数跳出,直到调用await_resume后再次跳入协程函数执行余下操作。
2 可以看到在“co_test2 result”打印之前,main函数已执行完成,等到await_resume后依然会跳回协程函数并执行余下部分。


co_yield 让出操作
需要额外定义yield_value函数。
1 执行co_yield 会跳转到yield_value函数中,通过resume以及promise操作获取结果。


3 代码用例
以下代码在linux下测试,gcc版本需要 linux-gcc10.1以上。
编译指令:g++ faw.cpp -fcoroutines -std=c++20
#include <iostream>
#include <coroutine>
#include <thread>
#include <functional>
#include <chrono>template <typename... Args>
void print_log(const char* fmt, Args... args) {char log_buf[128] = { 0 };snprintf(log_buf, 128, fmt, args...);char time_buf[64] = { 0 };unsigned long tid = pthread_self();char buf[160] = { 0 };snprintf(buf, 160, "[%lu] [%s]", tid, log_buf);std::cout << buf << std::endl;
}using callback_t = std::function<void(int)>;
void async_op(int value, callback_t cb) {std::thread t([value, cb]() {std::this_thread::sleep_for(std::chrono::milliseconds(1000));cb(value+1000);});t.detach();
}struct MyTask {struct promise_type;using handle_t = std::coroutine_handle<promise_type>; //yield操作MyTask() {}MyTask(handle_t handle): handle_(handle) {}struct promise_type {MyTask get_return_object() {print_log("get_return_object beg");return MyTask(handle_t::from_promise(*this));}std::suspend_never initial_suspend() {print_log("initial_suspend beg");return std::suspend_never();}std::suspend_never final_suspend() noexcept {print_log("final_suspend beg");return std::suspend_never();}//co_returnvoid return_void() {print_log("return_void beg");std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //测试异步}void unhandled_exception() {}//co_yieldauto yield_value(int v) {data_ = v;return std::suspend_always();}int data_ = 0;};int get_value() {handle_.resume();if(!handle_.done()){return handle_.promise().data_;}return -1;}handle_t handle_;
};//co_await操作
class AwaitOp {
public:AwaitOp(int value): input_(value), result_(0) {}bool await_ready() {print_log("await_ready beg");std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //测试阻塞情况return false;}void await_suspend(std::coroutine_handle<> handle) {auto cb = [handle, this](int value) mutable {result_ = value;print_log("----------");handle.resume(); //执行完后调回};async_op(input_, cb);}int await_resume() {print_log("await_resume beg");return result_;}
private:int input_;;int result_;
};#if 1
MyTask co_test1(){print_log("co_test1 beg");co_return; //业务跳转,协程函数退出print_log("co_test1 end");
}MyTask co_test2(){print_log("co_test2 beg");int input= 999;int result = co_await AwaitOp(input); //业务跳转,协程函数退出print_log("co_test2 result=%d",result);co_return;print_log("co_test2 end");
}#endif
MyTask co_test3_2(){print_log("co_test3_2 beg");int t = 99;co_yield t; //切换出去print_log("co_test3_2 end");
}void co_test3(){MyTask task = co_test3_2();int result = task.get_value(); //切换结果print_log("co_test3 result=%d",result);
}int main() {print_log("main beg");co_test1(); //co_return测试//co_test2(); //co_await测试//co_test3(); //co_yield测试print_log("main end");getchar();return 0;
}