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;
}

 

posted @ 2021-04-20 20:29  PKICA  阅读(35)  评论(0编辑  收藏  举报