1.协程是一种可以挂起和恢复的函数,适用于异步编程、生成器等场景。
2.一个函数如果包含以下关键字之一,即为协程函数:
    2.1 co_await:用于暂停协程的执行,直到一个等待的操作完成
        2.1.1 用法auto ret =  co_await async_fun();//async_fun为awaitable等待对象
        2.1.2 awaitable对象,它需要有3个成员函数:
              bool await_ready():检查操作是否完成(为false,表示需要暂停)
              {下面3个中的一个:
				void await_suspend(std::coroutine_handle<> handle):处理协程挂起逻辑,启动异步逻辑,通常会保存协程句柄以便后续恢复
				bool await_suspend(std::coroutine_handle<> handle):返回bool类型的
				std::coroutine_handle<> handle await_suspend(std::coroutine_handle<> handle):返回handle类型的
              }
			  T await_resume():恢复时调用,返回等待结果(T为结果类型)
        2.1.3 默认的两个awaitable对象std::suspend_always、std::suspend_never

    2.2 co_yield:用于生成一个值,然后暂停协程
        语法:co_yield value; // 等价于 co_await promise.yield_value(value)

    2.3 co_return:返回结果并结束协程
        co_return expression; //用于返回最终结果并终止协程,结果会通过promise_type传递给调用者

3.协程函数必须返回一个符合协程接口的类型
    3.1 这个类型通常是一个包含promise_type嵌套结构体的类或结构体
    3.2 这个类型必须是可构造的,以便在get_return_object函数中创建实例
    3.3 这个类型必须是可析构的,以确保在协程结束时能够正确清理资源
    3.4 这个类型必须提供方法来控制协程的执行,如提供promise_type成员变量handle

4.promise_type结构体的要求(负责协程从创建到结束的整个生命周期管理)
        4.1 必须包含一个get_return_object函数,该函数返回一个代表协程的对象
        4.2 必须包含一个initial_suspend函数,该函数返回一个可等待对象,用于决定协程在创建后是否立即挂起
        4.3 必须包含一个final_suspend函数,该函数返回一个可等待对象,用于决定协程在执行完co_return后是否挂起
        4.4 必须包含一个return_void(协程函数返回void)return_value(协程函数有返回值非void)函数
        4.5 必须包含一个unhandled_exception函数,当协程内部抛出未捕获的异常时,该函数会被调用,用于处理异常
        4.6 yield_value函数,配合co_yield语句,使协程能够暂停执行并向调用者返回值,同时保持协程的状态以便后续恢复执行。
            每次co_yield都会触发yield_value函数的调用,从而实现一系列值的生成和传递
for:
    struct TASK { //定义一个协程接口的类型
        struct promise_type { //包含promise_type嵌套结构体
            TASK get_return_object() { ... }
            std::suspend_always initial_suspend() { ... }
            std::suspend_always final_suspend() { ... }
            void unhandled_exception() { ... }
            std::suspend_always yield_value(T val) { ... }
            void return_void() {}
        };
        std::coroutine_handle<promise_type> handle;//用于外部控制协程(恢复、销毁)的轻量级指针
        TASK(...){...}
        ~TASK() {...} 
    };
    TASK coroutineFun() { //协程函数
        std::this_thread::sleep_for(std::chrono::seconds(200));
        co_return ;
    }

co_await流程测试:



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

static void _sleep() {
  std::cout << "sleep 200" << std::endl;
  std::this_thread::sleep_for(std::chrono::milliseconds(200)); //
}

struct Task {
  struct promise_type;
  using handle_type = std::coroutine_handle<promise_type>;
  // 承诺对象:协程内部状态管理者
  struct promise_type {
    // 这个函数负责返回一个代表协程的对象
    // 这个返回的对象包含了协程的句柄等信息,用于外部对协程进行控制,比如恢复协程执行、检查协程状态等。它是协程与外部世界交互的重要桥梁
    Task get_return_object() {
      std::cout << "get_return_object" << std::endl;
      _sleep();
      return Task(handle_type::from_promise(*this));
    }
    // 首次执行前是否立即挂起,它返回一个类型为std::suspend_always或std::suspend_never的对象
    // 如果返回std::suspend_always,协程在创建后会立即挂起,直到被显式恢复;
    // 如果返回std::suspend_never,协程会立即开始执行其函数体
    std::suspend_never initial_suspend() {
      std::cout << "initial_suspend" << std::endl;
      _sleep();
      return {};
    }
    // 该函数决定协程在执行完co_return后,是否挂起。同样返回std::suspend_always或std::suspend_never类型的对象
    // 返回std::suspend_always意味着协程在co_return后会挂起,此时可以进行一些清理工作(如果需要),然后再由外部决定是否销毁协程;
    // 返回std::suspend_never则表示协程执行完co_return后直接结束,其资源会被立即清理
    std::suspend_always final_suspend() noexcept {
      std::cout << "final_suspend" << std::endl;
      _sleep();
      return {};
    }
    // 它的主要作用是处理 co_yield 表达式产生的值,返回awaitable类型等待对象
    // 当协程执行到 co_yield 语句时,yield_value
    // 函数会被调用,负责存储或处理这个通过 co_yield
    // 返回的值,以便外部调用者能够获取
    std::suspend_always yield_value(int value) {
      current_value = value;
      return {};
    }
    // void return_void() noexcept  {
    //     std::cout << "return_void" << std::endl;
    //     _sleep();
    // }
    // 处理co_return:保存返回值
    void return_value(int v) {
      std::cout << "return_value=" << v << std::endl;
      current_value = v;
    }

    void unhandled_exception() {
      std::cout << "unhandled_exception" << std::endl;
      std::terminate();
    }
    int current_value = 222; // 存储协程返回值
    ~promise_type() { std::cout << "~promise_type" << std::endl; }
  };

  handle_type h;
  Task(handle_type h) : h(h) {
    std::cout << "construct Task" << std::endl;
    _sleep();
  }
  ~Task() {
    std::cout << "destroy Task" << std::endl;
    _sleep();
    if (h)
      h.destroy();
  }
};

struct awaitFun {
  // 检查操作是否完成(初始为false,需要暂停,所以执行await_suspend)
  bool await_ready() const noexcept {
    std::cout << "await_ready,done=" << done << std::endl;
    return done;
  }
  // 处理协程挂起逻辑,启动异步逻辑
  void await_suspend(std::coroutine_handle<> h) noexcept {
    std::cout << "await_suspend started" << std::endl;
    std::this_thread::sleep_for(
        std::chrono::seconds(2)); // 模拟异步操作,这里使用线程睡眠
    result = 123;
    done = true;
    std::cout << "await_suspend end,ret=" << result << std::endl;
    h.resume(); // 操作完成,恢复协程
  }
  int await_resume() const noexcept {
    std::cout << "await_resume,ret=" << result << std::endl;
    return result;
  } // 恢复时调用,返回等待结果(T为结果类型)
  bool done = false;
  int result = 0;
};

Task testAwait() {
  std::cout << "testAwait start" << std::endl;
  int ret = co_await awaitFun();
  std::cout << "testAwait end,ret=" << ret << std::endl;
}

int main() {
  Task t =
      testAwait(); // 调用testAwait()时,首先调用get_return_object->接着调用initial_suspend->返回std::suspend_always暂停执行
  std::cout << "Result: " << t.h.promise().current_value << std::endl; // 输出8
  std::cout << "resume end" << std::endl;
  return 0;
}
输出:
construct Task
sleep 200
initial_suspend
sleep 200
testAwait start
await_ready,done=0
await_suspend started
await_suspend end,ret=123
await_resume,ret=123
testAwait end,ret=123
final_suspend   #返回suspend_always,所以其不会立即析构~promise_type,而是在~Task里面调用destroy析构,如果返回suspend_never会立即析构~promise_type,那么在~Task里面调用destroy再次析构会崩溃
sleep 200
Result: 222
resume end
destroy Task
sleep 200
~promise_type

用co_yield产生序列:

#include <iostream>
#include <coroutine>
#include <vector>

// 生成器返回类型
struct FibonacciGenerator {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    handle_type h;
    FibonacciGenerator(handle_type h) : h(h) {}
    ~FibonacciGenerator() { if (h) h.destroy(); }

    struct promise_type {
        FibonacciGenerator get_return_object() {
            return FibonacciGenerator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
        // 用于存储生成的值
        int current_value;
        // 修正后的yield_value函数,接受int类型参数
        std::suspend_always yield_value(int value) {
            current_value = value;
            return {};
        }
    };

    bool move_next() {
        if (h.done()) return false;
        h.resume();
        return!h.done();
    }

    int current() {
        return h.promise().current_value;
    }
};

// 协程函数
FibonacciGenerator fibonacci_generator(int limit) {
    int a = 0;
    while (a < limit) {
        co_yield a++;
    }
}

int main() {
    FibonacciGenerator gen = fibonacci_generator(10);
    while (gen.move_next()) {
        std::cout << gen.current() << " ";
    }
    std::cout << std::endl;
    return 0;
}
输出:0 1 2 3 4 5 6 7 8 9

协程文件读取:

#include <iostream>
#include <coroutine>
#include <thread>
#include <string>
#include <fstream>

class FileReader{
public:
    struct promise_type;
    using handle_type  = std::coroutine_handle<promise_type>;
    handle_type h_;
    FileReader(handle_type h):h_(h)
    {

    }
    ~FileReader()
    {
        if(h_)
            h_.destroy();
    }
    struct promise_type
    {
        FileReader get_return_object() {
            return FileReader(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
        // 用于存储生成的值
        std::string curLine_;
        // yield_value 函数处理 co_yield 产生的值
        std::suspend_always yield_value(std::string &value) {
            curLine_ = value;
            return {};
        }
    };

    bool next_line()
    {
        //执行co_return后,h_.done()将返回true
        if (h_.done()) return false;
        h_.resume();
        return!h_.done();
    }

    std::string line(){
        return h_.promise().curLine_;
    }
};

bool filterOne(const std::string & line)
{
    return line.find("error") != std::string::npos;
}

// 协程函数:按行读取文件并过滤
FileReader readFileAndFilter(const std::string& filename) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        std::cerr << "无法打开文件: " << filename << std::endl;
        co_return;
    }
    std::string line;
    while (std::getline(file, line)) {
        if (!filterOne(line)) {
            co_yield line;
        }
    }
    file.close();
    co_return;//执行 co_return 后,FileReader.h.done() 将返回 true
}
int main() 
{
    FileReader reader = readFileAndFilter("./example.txt");
    while (reader.next_line()) {
        std::cout << reader.line() << std::endl;
    }
    return 0;
}

无栈协程函数:帧堆分配与释放规则验证

#include <coroutine>
#include <iostream>
#include <memory>

// 协程返回类型(Promise类型)
struct MyTask {
  struct promise_type {
    // 协程帧的内存分配器(默认堆分配)
    static void *operator new(std::size_t size) {
      std::cout << "协程帧分配内存:" << size << " 字节(堆上)" << std::endl;
      return ::operator new(size);
    }
    static void operator delete(void *ptr, std::size_t size) {
      std::cout << "协程帧释放内存:" << size << " 字节" << std::endl;
      ::operator delete(ptr);
    }

    MyTask get_return_object() {
      return MyTask{std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }
  };

  std::coroutine_handle<promise_type> handle;
  ~MyTask() {
    /*
    核心知识点总结
        1.done()状态与final_suspend的关联,
            只要进入final_suspend协程的done()状态就为true.
        2.final_suspend()返回uspend_never时,协程生命周期彻底结束,
            底层会自动调用operator delete函数释放协程帧,
        3.final_suspend()返回suspend_always时,协程挂起,底层不会自动释放协程帧
    destroy()的调用规则
        1.destroy()会手动调用operator delete 释放协程帧
        2.对done()=true的句柄调用destroy(),会与底层自动释放逻辑冲突,
            触发double free 崩溃
        3.仅对 done()=false(协程未完成 / 挂起)的句柄调用一次才安全
    最佳实践
        1.析构函数中始终保留if (handle && !handle.done()) { handle.destroy();},
            既避免崩溃,也防止挂起场景的内存泄漏。
    */
    if (handle && !handle.done()) {
      handle.destroy();
    }
  }
};

// 无栈协程函数(帧堆分配)
MyTask coro_func(int n) {
  // 局部变量存储在堆上的协程帧中,而非线程栈
  int local_data[4096] = {n}; // 模拟16k左右的帧大小
  std::cout << "协程运行中,局部变量地址:" << &local_data << "(堆地址)"
            << std::endl;
  co_return;
}

int main() {
  // 线程栈地址范围(可对比协程帧地址)
  int stack_var;
  std::cout << "线程栈变量地址:" << &stack_var << std::endl;
  // 创建1000个协程(仅堆内存增加,线程栈无压力)
  for (int i = 0; i < 1000; ++i) {
    auto task = coro_func(i);
  }

  return 0;
}
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐