我是靠谱客的博主 爱笑心锁,最近开发中收集的这篇文章主要介绍C++20新特性—coroutine协程(一),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

所谓“协程”,简单点说就是一个函数可以中止运行,待“条件成熟”后,再次在断点的地方重新运行,即能以“进入-暂停-继续-暂停…”这样的方式运行函数,因此讨论协程,需要关心下面几个问题:

  • 在运行点暂停,然后再运行—暂停点的记录
  • 暂停时的运行环境,恢复运行时,应首先恢复当时的环境—运行环境的记录
  • 因协程状态是变化的,因此需要有办法查询协程当前的运行状态,包括返回值、异常等—协程状态的记录

异步操作是 “慢”资源与“快”资源协调编程方面的一个“老”问题,协程的目标就是试图以“同步的形式”编写出异步操作的效果。
以前,C++协程的实现可以通过第三方库来实现的,例如Boost就有两种协程实现(Boost.coroutine2中的协程和Boost.Asio中的协程,见笔者的博文),现在,协程从语言标准层面得以支持,也就是不需要第三方库了。
C++20增加了库<coroutine>,需要协程支持时应包含该文件(【注】gcc10.2编译时需要添加-fcoroutines)。
C++的协程是无栈(stackless)协程,与一个协程关联的有三个对象:
一个promise_type内部对象,保存协程的结果(或发生的异常)
一个handle,从外部可以用来恢复执行或销毁协程
一个可保存coroutine state的内部对象,从堆中分配空间(因而是stackless),包括以下内容:

  • 所有的函数参数(值参数和引用参数,这里需要注意的是引用参数,如果协程的生命期比被引用的参数长,则这些引用参数会因不可用而变得很“危险”)
  • 当前中断点
  • 那些跨越中断点前后的(局部)变量

为表达协程,C++引入了三个关键字:co_return,co_yield和co_await。
co_await:一元操作符。co_await用法为co_await expr,其中expr需要满足一定条件(即awaitable),它要含有以下三个函数:

bool await_ready()
void await_suspend(std::coroutine_handle<> h)
T await_resume()

co_yield :挂起运行,记录状态,返回结果
co_return:结束状态,协程结束

以下类在协程中被经常使用,先需要说明一下它们。

  • future:用于获得“将来”状态(异步操作的结果,当前还未完成)。它在C++11时出现,是一个可跨线程的运行结果的包装器,future可以阻塞方式得到异步操作的结果。
  • std::suspend_always和std::suspend_never:它们是两个被预定义且满足co_await接口的类,前者的await_ready返回false,后者则返回true。
  • coroutine_traits:在编译阶段用来查找和检测满足特定coroutine的promise_type。
  • std::coroutine_handle:类似于文件句柄对文件的标识,coroutine_handle用来标识、引用coroutine,例如可以检测、运行、销毁coroutine。coroutine_handle需要一个promise类型作为构造的模板参数,外部可通过promise()函数得到这个特定的promise类。系统库同时定义了一个std::noop_coroutine_handle的类,这个类所代表的coroutine不做任何事情,会挂起。

协程是特殊函数,在一些语言中,例如javascript,python中, 只需在一个函数前添加async关键字就可以定义一个协程函数,但其实它的背后存在大量操作逻辑。虽然现在C++20引入了一些关键字,但目前的实现上,使用协程还不像javascript,python中那么方便,需要为实现这个特殊函数做一些额外的工作。
协程函数的一般构成为:

R coroutine_fuction(args…)
{
co_wait  expr
或
co_yield expr
或
co_return expr
//没有return
}

其中R是返回类型。
编译器发现有coroutine函数时,会根据R,args查找struct std::coroutine_traits<R, args…>,如果没有,则产生一个,如果已被定义,就用已被定义的,成功后,检查struct std::coroutine_traits<R, args…>::promise_type是否满足如下描述的coroutine接口:

coroutine接口要求具有public promise_type结构,而promise_type中必须有get_return_object,initial_suspend,final_suspend,unhandled_exception四个函数,另外,如有co_return调用,则promise_type结构中需要return_void/return_value函数;有co_yield调用时,需要yield_value函数;有co_await调用时,需要重载操作符co_await await_transform(expr)或expr,其中await_transform是promise_type结构中一个可选的函数。

如果成功的话,就可以认为该函数是正常的协程函数了。
实际的情况也许比较复杂,因为coroutine_fuction可能是静态函数,非静态函数或右值的非静态函数,对此,cppreference是这样描述的:

  • 静态函数时,promise_type的定义为std::coroutine_traits<R, Args...>::promise_type,如task<float> foo(std::string x, bool flag)对应std::coroutine_traits<task<float>, std::string, bool>::promise_type
  • 非静态函数时,promise_type的定义为std::coroutine_traits<R, ClassT &, Args...>::promise_type,如task<void> my_class::method1(int x) const对应std::coroutine_traits<task<void>, const my_class&, int>::promise_type
  • 右值非静态函数时,promise_type的定义为std::coroutine_traits<R, ClassT&&, Args...>::promise_type,如task<void> my_class::method1(int x) &&对应std::coroutine_traits<task<void>, my_class&&, int>::promise_type

由此,协程的大致运行流程如下:

下面一个示例说明co_return 的用法。

template <typename T>
struct myfuture: std::future<T>
{
    myfuture(std::future<T> && a):std::future<T>(std::move(a))
    {
         std::cout<<"myfuture"<<std::endl;
    }
};
template <typename T, typename... Args>
requires(!std::is_void_v<T> && !std::is_reference_v<T>)
struct std::coroutine_traits<myfuture<T>, Args...> 
{
  struct promise_type:std::promise<T> 
  {
    std::suspend_never initial_suspend() const noexcept { std::cout<<"initial_suspend"<<std::endl;  return {}; }
    std::suspend_never final_suspend() const noexcept {   std::cout<<"final_suspend"<<std::endl; return {}; }
    void unhandled_exception() noexcept { this->set_exception(std::current_exception()); }    
    myfuture<T> get_return_object() noexcept 
    {
        std::cout<<"get_return_object"<<std::endl;
        return this->get_future();
    }
    
    void return_value(const T &value) noexcept(std::is_nothrow_copy_constructible_v<T>) 
    {
        std::cout<<"return_value"<<std::endl;        
      this->set_value(value);
    }
    void return_value(T &&value) noexcept(std::is_nothrow_move_constructible_v<T>)
    {
      this->set_value(std::move(value));
    }
  };
};
myfuture<int> co_fun(float a, float b) 
{
  std::cout << "============co_fun"<<std::endl;        
  co_return (int)(a * b);
}
int main() 
{
   auto res1= co_fun(1.1,2.0);
   std::cout << "============co_fun return:"<<res1.get()<<std::endl;     
}

代码输出如下:

get_return_object
myfuture
initial_suspend
============co_fun
final_suspend
============co_fun return:2

在进入运行co_fun之前,有三行输出,这就是协程流程图中的“准备部分”,由于在initial_suspend中返回的是std::suspend_never,所以程序没有进入suspend。进入co_fun1后,采用co_return 2返回,实际是调用generator:: promise_type::return_value(2),promise_type中的_val保存当前传入的值,退出co_fun1后,采用generator::get_value()得到该值。

以上代码并没有实际的意义,只是为了说明coroutine的流程。

最后

以上就是爱笑心锁为你收集整理的C++20新特性—coroutine协程(一)的全部内容,希望文章能够帮你解决C++20新特性—coroutine协程(一)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部