C++20新特性二:协程coroutine
C++20加入协程,其实是为了以后加入网络库而准备的。协程也是异步执行的,那么它跟std::thread有什么区别呢?区别在于协程是用户调度的,线程则是系统调度的。由于是用户态的,所以协程数量是不受限制的,想要多少就创建多少。同时C++20的协程是无栈协程,在调度切换时性能比线程快很多。基于协程的特点,基本上协程就是为了网络库而服务的了。应用开发者不建议使用协程,除非你明确知道协程的优势所在,否则不建议使用协程(std::async不香吗)。
传统异步调用
协程出现以前,异步函数也许是这么写的
using AsyncResultsCallback = std::function<void(const std::string&)>; // 异步获取结果函数 void async_get_results(AsyncResultsCallback callback) { std::thread t([callback]() { std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟耗时的操作 callback("seconds later"); }); t.detach(); // 测试代码,实际项目中不要使用detach }
调用方式 async_get_results([] (const std::string& str){ std::cout<<"async get results: "<<str<<std::endl; });
协程的使用例子
协程一般需要定义三个东西:协程体(coroutine),协程承诺特征类型(Traits),await对象(await)。
要调用协程,首先得定义协程体。
协程体的定义和普通函数一样,但是协程体里面有co_await、co_return、co_yield关键字。也就是说只要函数体里面带有co_await、co_return、co_yield三个关键字中的一个,那么这个函数体就是协程(coroutine)。先看看协程定义例子:
// 协程体 Traits coroutine_get_results(AsyncResultsCallback callback) { // 这时还在主线程中 std::cout<<"run at main thread id:"<<std::this_thread::get_id()<<std::endl; const std::string& ret = co_await AsyncResultsWaitable(); // 这时已经是在子线程中了 std::cout<<"run at slaver thread id:"<<std::this_thread::get_id()<<std::endl; callback(ret); }
协程体定义和执行规则跟函数体是不一样的!这点一定要明确,否则你可能理解不了协程的写法
Traits并不是返回类型,而是协程的特征类型。协程体里面的语句可能会在不同的线程中执行,这个是最大的特点!co_await以后就在await对象的await_suspend(如果await_ready返回false)函数去执行了。等co_await语句执行完之后,这时候可能已经是在另一条线程中了。
再看下协程特征类型的定义:
// 给协程体使用的承诺特征类型 struct Traits { struct promise_type { // 协程体被创建时被调用 auto get_return_object() { return Traits{}; } // get_return_object之后被调用 auto initial_suspend() { return std::suspend_never{}; } // return_void之后被调用 auto final_suspend() { return std::suspend_never{}; } void unhandled_exception() { std::terminate(); } // 协程体执行完之后被调用 void return_void() { } }; };
协程特征类型主要是用于一些事件的响应。
在看下await对象的定义:
// 协程使用的await对象 struct AsyncResultsWaitable { // await是否已经计算完成,如果返回true,则co_await将直接在当前线程返回 bool await_ready() const { return false; } // await对象计算完成之后返回结果 std::string await_resume() { return _result; } // await对象真正调异步执行的地方,异步完成之后通过handle.resume()来是await返回 void await_suspend(std::coroutine_handle<> handle) { async_get_results([handle, this] (const std::string& str) { _result = str; handle.resume(); }); } std::string _result; // 将返回值存在这里 };
await_ready:用来判断当前await对象是否已经计算完成,如果返回false则await_suspend将不会被调用co_await语句也将直接返回结果。
await_resume:用来返回await对象的计算结果。
await_suspend: await对象正在执行异步操作的地方。
协程调用例子:
// 通过协程异步获取结果 coroutine_get_results([] (const std::string& str){ std::cout<<"coroutine get results: "<<str<<std::endl; });
#include <iostream> #include <functional> #include <future> #include <thread> #include <string> #include <coroutine> using AsyncResultsCallback = std::function<void(const std::string&)>; // 异步获取结果函数 void async_get_results(AsyncResultsCallback callback) { std::thread t([callback]() { std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟耗时的操作 callback("seconds later"); }); t.detach(); // 测试代码,实际项目中不要使用detach } // 给协程体使用的承诺特征类型 struct Traits { struct promise_type { // 协程体被创建时被调用 auto get_return_object() { return Traits{}; } // get_return_object之后被调用 auto initial_suspend() { return std::suspend_never{}; } // return_void之后被调用 auto final_suspend() { return std::suspend_never{}; } void unhandled_exception() { std::terminate(); } // 协程体执行完之后被调用 void return_void() { } }; }; // 协程使用的await对象 struct AsyncResultsWaitable { // await是否已经计算完成,如果返回true,则co_await将直接在当前线程返回 bool await_ready() const { return false; } // await对象计算完成之后返回结果 std::string await_resume() { return _result; } // await对象真正调异步执行的地方,异步完成之后通过handle.resume()来是await返回 void await_suspend(std::coroutine_handle<> handle) { async_get_results([handle, this] (const std::string& str) { _result = str; handle.resume(); }); } std::string _result; // 将返回值存在这里 }; // 协程体 Traits coroutine_get_results(AsyncResultsCallback callback) { // 这时还在主线程中 std::cout<<"run at main thread id:"<<std::this_thread::get_id()<<std::endl; const std::string& ret = co_await AsyncResultsWaitable(); // 这时已经是在子线程中了 std::cout<<"run at slaver thread id:"<<std::this_thread::get_id()<<std::endl; callback(ret); } int main(int argc, char *argv[]) { // 传统异步获取结果 async_get_results([] (const std::string& str){ std::cout<<"async get results: "<<str<<std::endl; }); // 通过协程异步获取结果 coroutine_get_results([] (const std::string& str){ std::cout<<"coroutine get results: "<<str<<std::endl; }); getchar(); return 1; }