红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【C++】协程

协程

协程函数

若一个函数中出现了以下三个关键字之一,则编译器就知道这是一个协程函数

  • co_await
  • co_yield
  • co_return

并且,协程函数必须返回一个符合要求的协程对象,若有返回值,需要通过协程对象获得

协程对象

协程函数的返回值是自定义的一个协程对象

模板

通常需要内嵌一个名为promise_type的结构定义,内部需要实现一些函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
#include<coroutine>
struct MyCoroutine{
  struct promise_type {
    MyCoroutine get_return_object() {
      return MyCoroutine();
    }
    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
  };
};
MyCoroutine coroFunc(){
  std::cout <<"hello world" << std::endl;
  co_return;
}
int main(){
  MyCoroutine coro = coroFunc();
  return 0;
}

此时coroFunc刚开始执行就被挂起,MyCoroutine对象也没有实现任何协程,故后面的代码不会执行,稍微进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
#include<coroutine>
struct MyCoroutine{
  struct promise_type {
    MyCoroutine get_return_object() {
      // 获得协程句柄
      return MyCoroutine(std::coroutine_handle<promise_type>::from_promise(*this));
    }
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
  };
  // 通过构造函数传给新创建的对象
  // 调用了协程句柄的resume函数
  MyCoroutine(std::coroutine_handle<promise_type> h) : handle(h) {}
  void resume() {
    if (handle) {
      handle.resume(); // 恢复协程执行
    }
  }
private:
  std::coroutine_handle<promise_type> handle; // 封装了一个指向协程帧的指针,并提供函数
};
MyCoroutine coroFunc(){
  std::cout <<"hello world" << std::endl;
  co_return;
}

int main(){
  MyCoroutine coro = coroFunc();
  coro.resume(); // 恢复协程执行
  return 0;
}

返回值

若需要提供返回值,则需要将return_void改为return_value,并添加get函数存储并获取反返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<iostream>
#include<coroutine>
struct MyCoroutine{
  struct promise_type {
    MyCoroutine get_return_object() {
      // 获得协程句柄
      return MyCoroutine(std::coroutine_handle<promise_type>::from_promise(*this));
    }
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_value(int v) {_value = v;}
    void unhandled_exception() {}
    public:
      int get(){return _value;}
    private:
      int _value; // 存储协程返回值
  };
  // 通过构造函数传给新创建的对象
  // 调用了协程句柄的resume函数
  MyCoroutine(std::coroutine_handle<promise_type> h) : handle(h) {}
  int get(){
    handle.resume(); // 恢复协程执行
    return handle.promise().get(); // 获取协程返回值
  }
private:
  std::coroutine_handle<promise_type> handle; // 封装了一个指向协程帧的指针,并提供函数
};
MyCoroutine coroFunc(){
  std::cout <<"hello world" << std::endl;
  co_return 666;
}

int main(){
  MyCoroutine coro = coroFunc();
  int value = coro.get();
  std::cout << "value= " << value << std::endl;
  return 0;
}

co_yield

如果要使用co_yield,需要添加yield_value函数

由于co_yield不是函数执行的结尾,故需要返回一个可等待对象,并使用co_await对该对象进行等待,即

1
2
co_yield v;
co_await promise.yield_value(v);

或者返回一个suspend_always对象,让程序挂起

1
auto yield_value(int v) { _value = v; return std::suspend_always{};}

为方便使用,还可以重载一下布尔运算符,通过调用handle的done函数来判断协程是否执行完毕

1
operator bool(){return !handle.done();}

在主函数中,就可以通过一个循环对协程执行进行挂起和继续操作,并获得每次的返回值

1
2
3
4
5
MyCoroutine coro = coroFunc();
while(coro){
    int value = coro.get();
    std::cout << "value= " << value << std::endl;
}

应用:实现发生器(Generator)

一个斐波那契数列生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<iostream>
#include<coroutine>
struct MyCoroutine{
  struct promise_type {
    MyCoroutine get_return_object() {
      // 获得协程句柄
      return MyCoroutine(std::coroutine_handle<promise_type>::from_promise(*this));
    }
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_value(int v) {_value = v;}
    auto yield_value(int v) { _value = v; return std::suspend_always{};}
    void unhandled_exception() {}
    public:
      int get(){return _value;}
    private:
      int _value; // 存储协程返回值
  };
  // 通过构造函数传给新创建的对象
  // 调用了协程句柄的resume函数
  MyCoroutine(std::coroutine_handle<promise_type> h) : handle(h) {}
  operator bool(){return !handle.done();} // 重载bool运算符,判断协程是否完成
  int get(){
    handle.resume(); // 恢复协程执行
    return handle.promise().get(); // 获取协程返回值
  }
private:
  std::coroutine_handle<promise_type> handle; // 封装了一个指向协程帧的指针,并提供函数
};

MyCoroutine fib(){
  int a = 0, b = 1, n = 40;
  co_yield a;
  co_yield b;
  while(n--){
    int r = a+b;
    co_yield r; // 每生成一项就把协程挂起,等待外部调用resume函数
    a = b;
    b = r;
  }
  co_return a+b;
}

int main(){
  MyCoroutine coro = fib();
  while(coro){
    int value = coro.get();
    std::cout << "value= " << value << std::endl;
  }
  return 0;
}

co_await

语法:co_await 表达式

表达式可以是可等待对象,重载co_await()运算符,也可以是一个promise类型的await_transform()函数,将表达式作为函数参数并返回可等待对象

可等待类型awaitable的定义如下:

1
2
3
4
5
6
struct awaitable
{
    bool await_ready();
    void await_suspend(std::coroutine_handle<> h);
    auto await_resume();
};

调用对象时,首先调用await_ready(),若返回值为false,则调用await_suspend()让程序挂起,当协程继续执行后,会接下来执行await_resume(),若返回值为true,则直接执行await_resume()

内置类型suspend_always中await_ready()返回false,其余两个函数什么都不做,作用是将程序挂起

内置类型suspend_never中await_ready()返回true,其余两个函数什么都不做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<coroutine>
#include<iostream>
#include<thread>
struct promise;
struct MyCoroutine: std::coroutine_handle<promise>{
  using promise_type = ::promise;
};

struct promise{
  MyCoroutine get_return_object() {
    return {MyCoroutine::from_promise(*this)};
  }
  auto initial_suspend() { return std::suspend_always{}; }
  auto final_suspend() noexcept { return std::suspend_always{}; }
  void unhandled_exception() {}
};

struct awaitable{
  bool await_ready(){
    std::cout << "await_ready called" << std::endl;
    return false; // 返回false表示协程需要挂起
  }
  void await_suspend(std::coroutine_handle<> h){
    std::cout << "await_suspend called" << std::endl;
    // 在这里可以执行一些异步操作
    // ...
      h.resume(); // 恢复协程执行
  }
  auto await_resume() {
    std::cout << "await_resume called" << std::endl;
    return 666; // 返回协程的结果
  }
};  

MyCoroutine coroFunc(){
  std::cout << "coroFunc started" << std::endl;
  co_await awaitable{}; // 使用协程挂起
  std::cout << "coroFunc resumed" << std::endl;
}

int main(){
  MyCoroutine coro = coroFunc();
  std::cout << "main started" << std::endl;
  coro.resume(); // 恢复协程执行
  std::cout << "main finished" << std::endl;
  return 0;
}
1
2
3
4
5
6
7
main started
coroFunc started
await_ready called
await_suspend called
await_resume called
coroFunc resumed
main finished

根据返回结果,证明了函数调用的顺序是ready->suspend->resume