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

概述

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

template<class T>
struct generator
{
  struct promise_type
  {
    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 { }    
    generator<T> get_return_object() noexcept 
    {
        std::cout<<"get_return_object"<<std::endl;  return {};
    }
    auto await_transform(T id)
    {
      return await_op {id};        
    }
      struct await_op
      {
        bool await_ready() const noexcept 
        {
          std::cout<<"await_ready"<<std::endl; 
          return _id%2;
        }
        void await_suspend(std::coroutine_handle<> cont) const 
        {
          std::cout<<"await_suspend"<<std::endl; 
        }
        T await_resume()
        {
             std::cout<<"await_resume"<<std::endl;
             return _id+1;
        }
        T _id;
      };    
  };
};
generator<int> co_fun2() 
{
  std::cout << "============co_fun2"<<std::endl;  
  int id=co_await 1;
  std::cout << "co_await:"<<id<<std::endl;  
}
generator<int> co_fun3() 
{
  std::cout << "============co_fun3"<<std::endl;  
  int id=co_await 2;
  std::cout << "co_await:"<<id<<std::endl;  
}

int main() 
{
   co_fun2();
    std::cout<<std::endl;       
   co_fun3();
    std::cout<<"end of main"<<std::endl;    
}

输出如下:

get_return_object
initial_suspend
============co_fun2
await_ready
await_resume
co_await:2
final_suspend

get_return_object
initial_suspend
============co_fun3
await_ready
await_suspend
end of main

上例中,编写了一个满足awaitabble接口的await_op结构,由await_ transform返回,这是构造co_await expr的一种方法(另外一种见下例)。主要由于co_await的输入不同,await_ready的返回也不同,co_fun2, co_fun3的执行顺序如下图所示。

下面是co_await另一个有些实用意义的示例。

template<typename T>
void println(const char *title, T content)
 {
    static std::mutex mtx;
    const std::lock_guard<std::mutex> lock(mtx);
    std::time_t tm = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 
    std::cout<<"["<<tm<<","<<std::this_thread::get_id()<<"]"<<title<<" "<<content<<std::endl;    
}
//==============================================================================
template <typename T, typename... Args>
requires(!std::is_void_v<T> && !std::is_reference_v<T>)
struct std::coroutine_traits<std::future<T>, Args...> 
{
  struct promise_type:std::promise<T> 
  {
    std::suspend_never initial_suspend() const noexcept { println("initial_suspend","");   return {}; }
    std::suspend_never final_suspend() const noexcept {  println("final_suspend","");  return {}; }
    void unhandled_exception() noexcept { this->set_exception(std::current_exception()); }    
    std::future<T> get_return_object() noexcept 
    {
         println("get_return_object","");
        return this->get_future();
    }
    void return_value(const T &value) noexcept(std::is_nothrow_copy_constructible_v<T>) 
    {
      this->set_value(value);
    }
    void return_value(T &&value) noexcept(std::is_nothrow_move_constructible_v<T>)
    {
        println("return_value","");  
      this->set_value(std::move(value));
    }
  };
};
using namespace std::chrono_literals;
template <typename T>
auto operator co_await(std::future<T> future) noexcept
requires(!std::is_reference_v<T>) 
{
  struct awaiter : std::future<T> 
  {
    bool await_ready() const noexcept 
    {
         println("await_ready","");  
        return this->wait_for(0s) != std::future_status::timeout;
    }
    void await_suspend(std::coroutine_handle<> cont) const 
    {
        println("await_suspend","");      
      std::thread([this, cont] {
          
        println("wait for worker","");     
        this->wait();
        println("worker OK","");          
        cont.resume();
      }).detach();
    }
    T await_resume()
    { 
        println("await_resume","");             
        return this->get(); 
    }
  };
  return awaiter{std::move(future)};
}

std::future<int> co_fun4(float a, float b) 
{
   println("co_fun4","");
   int r=2;
  float f = co_await std::async(std::launch::async,[a](){
           println("async start","");
          std::this_thread::sleep_for(1s); 
           println("end of sleep","");      
          return a*2.0;          
      });

  println("end of co_await",f*r);    
  co_return (int)(f*b);
}
int main() 
{
   println("main","");   
   auto res2= co_fun4(2.1,2.0);
   println(" wait for co_fun4 return:","");  
   int i=res2.get();
   println("end of  main",i);     
}

输出如下:

[1608685871,139856935995200]main 
 [1608685871,139856935995200]get_return_object 
 [1608685871,139856935995200]initial_suspend 
 [1608685871,139856935995200]co_fun4 
[1608685871,139856935995200]await_ready 
 [1608685871,139856935995200]await_suspend 
 [1608685871,139856935995200] wait for co_fun4 return: 
 [1608685871,139856918120192]async start 
 [1608685871,139856909727488]wait for worker 
 [1608685872,139856918120192]end of sleep 
 [1608685872,139856909727488]worker OK 
 [1608685872,139856909727488]await_resume 
 [1608685872,139856909727488]end of co_await 8.4
 [1608685872,139856909727488]return_value 
 [1608685872,139856909727488]final_suspend 
 [1608685872,139856935995200]end of  main 8

上面的输出有点“混乱”,其实上面程序中产生了3个线程,一个主线程main,一个由std::async产生的工作线程(称为worker),一个在await_suspend中产生的等待-通知线程(称为notifier)。运行示意如下图所示。

在co_await以前,只有main在运行,因而输出到“============co_fun4”。进入co_await,costd::async 产生了线程worker,main依然在运行,wait_ready结果为false,进入await_suspend,产生线程notifier,main继续运行,因没有resume,跳出协程函数,返回到调用者,直到future.get产生阻塞为止。Notifier线程中future等待worker运行完成,如果worker完成,coroutine_handle恢复,调用await_resume(此后的协程恢复部分都是在notifier中运行),co_await返回得到结果,结果再由co_return返回。

注意协程resume后,协程输入参数b及局部变量r都得到恢复,说明它们之前都得到了暂存处理。

编写协程的目的是降低异步操作的难度,使它们看起来像是同步操作,因而调用者运行在一个线程,异步操作通常在另一个线程,但通过future可将结果安全地带回调用者线程(future自带同步机制),我们编写的部分(调用者线程)没有数据竞争,也不需要锁。

本文未涉及协程函数发生异常的情况,发生异常时,unhandled_exception将被调用,请参阅有关文档。

从上面的示例,可以看出目前写一个coroutine还比较麻烦,协程函数“隐含的规则”比较多,隐藏了我们看不到的逻辑,coroutine的风格似乎与以前C/C++严谨、“可控”的风格有点不同。但不管如何,C++的coroutine会改变我们的一些想法。coroutine的出现,同时会影响到现有第三方库中异步操作的实现,不知道它们会不会提供协程的方式?偶然看到C++23中将coroutine的使用列为一项重要内容,看来甜度更大的语法糖已经在路上,它们如何简化现有的写法,使之更便捷,我们还是拭目以待吧。

 

最后

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

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

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

评论列表共有 0 条评论

立即
投稿
返回
顶部