C++20 协程体验

时间:2021-07-04 00:45:46

1 介绍

  • 协程是比线程更加轻量级并发编程方式,CPU资源在用户态进行切换,CPU切换信息在用户态保存。

  • 协程完成异步的调用流程,并对用户展示出同步的使用方式。

  • 协程的调度由应用层决定,所以不同的实现会有不同的调度方式,调度策略比较灵活。

  • 协程是基于线程之上运行,同一个线程中,协程是串行的,不会产生线程资源的竞争,不同的协程间却是相互交叉运行的,只要依赖的线程没有终止,协程最终会跳转回来。

  • 协程可以充分利用单核CPU的资源,但是不太好利用多核CPU资源。

  • c++20 协程使用三大关键字 co_wait,co_return,co_yield

  • 在函数中使用到以上关键字的函数被称为协程函数,并且通过该关键字完成跳转。

2 使用

如果要使用协程函数,需要定义promise_type以及基本成员函数实现。

包括get_return_object、initial_suspend、final_suspend、unhandled_exception。

C++20 协程体验

演示代码最下面展示

co_return 执行完协程函数并返回结果

需要额外定义return_void函数。

流程分析:

1 可以看出调用co_return跳转到return_void,return_void执行完后,main函数向下执行。

2 "co_test1 end"并没有打印,说明协程函数co_test1分割开来,通过co_return切换了CPU资源,使主线程继续执行。

C++20 协程体验
C++20 协程体验
C++20 协程体验

co_await 执行到异步操作处,判断并进行挂起操作。

使用co_await 还需要再定义xxxx类并实现await_ready、await_suspend、await_resume函数。

1 协程函数中调用co_await后,跳转xxx的await_ready并判断是否就绪,如果是true,则协程函数调回继续运行,反之进入await_suspend挂起,协程函数跳出,直到调用await_resume后再次跳入协程函数执行余下操作。

2 可以看到在“co_test2 result”打印之前,main函数已执行完成,等到await_resume后依然会跳回协程函数并执行余下部分。

C++20 协程体验
C++20 协程体验

co_yield 让出操作

需要额外定义yield_value函数。

1 执行co_yield 会跳转到yield_value函数中,通过resume以及promise操作获取结果。

C++20 协程体验
C++20 协程体验

3 代码用例

以下代码在linux下测试,gcc版本需要 linux-gcc10.1以上。

编译指令:g++ faw.cpp -fcoroutines -std=c++20

#include <iostream>
#include <coroutine>
#include <thread>
#include <functional>
#include <chrono>

template <typename... Args>
void print_log(const char* fmt, Args... args) {
    char log_buf[128] = { 0 };
    snprintf(log_buf, 128, fmt, args...);
    char time_buf[64] = { 0 };
    unsigned long tid = pthread_self();
    char buf[160] = { 0 };
    snprintf(buf, 160, "[%lu] [%s]", tid, log_buf);
    std::cout << buf << std::endl;
}

using callback_t = std::function<void(int)>;
void async_op(int value, callback_t cb) {
      std::thread t([value, cb]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            cb(value+1000);
      });
      t.detach();
}

struct MyTask {
      struct promise_type;
      using handle_t = std::coroutine_handle<promise_type>;  //yield操作
      MyTask() {
      }
      MyTask(handle_t handle)
            : handle_(handle) {
      }
      struct promise_type {
            MyTask get_return_object() {
                  print_log("get_return_object beg");
                  return MyTask(handle_t::from_promise(*this));
            }
            std::suspend_never initial_suspend() {
                  print_log("initial_suspend beg");
                  return std::suspend_never();
            }
            std::suspend_never final_suspend() noexcept {
                  print_log("final_suspend beg");
                  return std::suspend_never();
            }

            //co_return
            void return_void() {
                  print_log("return_void beg");
                  std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //测试异步
            }
            void unhandled_exception() {}

            //co_yield
            auto yield_value(int v) {
                  data_ = v;
                  return std::suspend_always();
            }

            int data_ = 0;
      };

 int get_value() {
       handle_.resume();
       if(!handle_.done()){
            return handle_.promise().data_;
       }
       return -1;
 }

      handle_t handle_;
};

//co_await操作
class AwaitOp {
public:
      AwaitOp(int value)
            : input_(value), result_(0) {}

      bool await_ready() {
            print_log("await_ready beg");
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));  //测试阻塞情况
            return false;
      }

      void await_suspend(std::coroutine_handle<> handle) {
            auto cb = [handle, this](int value) mutable {
                  result_ = value;
                  print_log("----------");
                  handle.resume();   //执行完后调回
                  
            };
            async_op(input_, cb);
      }

      int await_resume() {
            print_log("await_resume beg");
            return result_;
      }
private:
      int input_;;
      int result_;
};

#if 1
MyTask co_test1(){
    print_log("co_test1 beg");
    co_return;   //业务跳转,协程函数退出
    print_log("co_test1 end");
}

MyTask co_test2(){
    print_log("co_test2 beg");
    int input= 999;
    int result = co_await AwaitOp(input);   //业务跳转,协程函数退出
    print_log("co_test2 result=%d",result);
    co_return;
    print_log("co_test2 end");
}

#endif
MyTask co_test3_2(){
    print_log("co_test3_2 beg");
    int t = 99;
    co_yield t;  //切换出去
    print_log("co_test3_2 end");
}

void co_test3(){
      MyTask task = co_test3_2();
      int result = task.get_value();  //切换结果
      print_log("co_test3 result=%d",result);
}

int main() {
      print_log("main beg");

      co_test1();     //co_return测试
      //co_test2();   //co_await测试
      //co_test3();   //co_yield测试
      print_log("main end");
      getchar();
      return 0;
}