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;
});
附上整个测试程序源码,编译环境 gcc 10.1:
#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;
}
最后,如果有不对的地方恳请大家多多指正,先谢谢了