• C++ 20 协程(三)


    C++ 20 协程(三)

    可等待体和等待器

    promise_type中的三个函数返回可等待体 yield_value, initial_suspend, final_suspend

    可等待体

    可等待体决定协程是否暂停

    本质上,编译器使用promise和co_await操作符生成这三个函数调用。

    image-20220928161637063

    co_await需要一个可等待体作为参数

    实现可等待体需要三个函数

    image-20220928161810250

    C++20标准已经定义了两个基本的对象:std::suspend_alwaysstd::suspend_never

    The Awaitable std::suspend_always

    struct suspend_always {
    constexpr bool await_ready() const noexcept { return false; }
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
    };
    

    总是挂起,await_ready返回false

    The Awaitable std::suspend_never

    struct suspend_never {
    constexpr bool await_ready() const noexcept { return true; }
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
    };
    

    从不挂起,await_ready返回true

    当协程协程执行的时候,这两个函数会自动执行:

    • 开始 initial_suspend
    • 结束 final_suspend

    initial_suspend

    当initial_suspend返回suspend_always时,协程会在开始时挂起;返回suspend_never时,则不会挂起

    A lazy coroutine

    std::suspend_always initial_suspend() {
    return {};
    }
    

    A eager coroutine

    std::suspend_never initial_suspend() {
    return {};
    }
    

    final_suspend

    在协程结束时执行,与几乎initial_suspend相同

    可等待体和等待器

    • 可被等待的对象称为可等待(awaitable )体或者表达式;
    • co_await运算符必须返回一个等待器(awaiter):
      • 可等待体和等待器可以是同一个类型;
      • std::future(实验)是可等待体。
      • co_await运算符返回等待器_Future_awaiter

    工作流

    00000000000000000000000000000000000

    编译器执行两个工作流外部的promise工作流和内部的awaiter工作流

    Promise 工作流

    当在函数中使用co_yield, co_await, co_return,函数成为一个协程,并且编译器将其转换成等价的如下代码

    The transformed coroutine

    image-20220928165209696

    主要工作步骤:

    • 协程开始执行:
      • 申请必要的协程帧空间
      • 拷贝所有函数参数到协程帧
      • 创建promise_type对象
      • 调用promise_type中的get_return_object方法创建协程句柄(coroutine handle),并保持在局部变量中,当协程第一次挂起时,将返回给调用者
      • 调用initial_suspend并且co_await其结果,可能返回suspend_always/never
      • co_await prom.initial_suspend恢复resume时,函数体执行
    • 协程到达挂起点:
      • 返回对象(prom.get_return_object())将返回给恢复协程的调用程序
    • 协程到达co_return
      • 调用prom.retrun_void/value没有返回值或者返回值
      • 销毁变量
      • 调用prom.final_suspend()并且co_await它的结果
    • 协程销毁
      • 调用promise_type对象和函数参数对象的析构函数
      • 释放协程帧的内存
      • 返还控制权给调用者
    • 调用co_await执行等待器工作流

    Awaiter工作流

    调用co_await会让编译器执行三个函数:await_ready await_suspend await_resume

    The generated Awaiter Workflow

    image-20220928171135751

    image-20220928171146726

    只有await_ready返回false时,流程才会执行,否则的话直接返回await_resume的结果

    await_ready返回false时:

    • 协程挂起,计算awaitable.await_suspend()的返回值,返回值有很多种情况

    image-20220928171651423

    出现异常情况不想写了

    co_return

    协程使用co_return作为返回语句

    template <typename T>
    struct MyFuture
    {
    	std::shared_ptr<T> value;
    
    	MyFuture(std::shared_ptr<T> p): value(p)
    	{
    	}
    
    	~MyFuture()
    	{
    	}
    
    	T get()
    	{
    		return *value;
    	}
    
    	struct promise_type
    	{
    		std::shared_ptr<T> ptr = std::make_shared<T>();
    
    		~promise_type()
    		{
    		}
    
    		MyFuture<T> get_return_object() { return ptr; }
    
    		void return_value(T v) { *ptr = v; }
    
    		std::suspend_never initial_suspend() { return {}; }
    
    		std::suspend_never final_suspend() noexcept { return {}; }
    
    		void unhandled_exception()
    		{
    			std::terminate();
    		}
    	};
    };
    
    MyFuture<int> createFuture()
    {
    	co_return 2021;
    }
    
    int main(int argc, char* argv[])
    {
    	auto fut = createFuture();
    
    	std::cout << fut.get() << std::endl;
    }
    
    

    image-20220928202106636

    • 流程
      • 初始化协程
        • 申请必要的协程帧空间
        • 拷贝所有函数参数到协程帧
        • 创建promise_type对象
        • 调用promise_type中的get_return_object方法将ptr传给fut
      • 调用co_return
        • 调用return_value并传入参数2022
      • 输出fut.get()

    co_yield

    无限数据流

    template <typename T>
    struct Generator
    {
    	struct promise_type;
    	using handle_type = std::coroutine_handle<promise_type>;
    
    
    	Generator(handle_type h): coro(h)
    	{
    	}
    
    	handle_type coro;
    
    	~Generator() { if (coro) coro.destroy(); }
    
    	Generator(const Generator&) = delete;
    	Generator& operator =(const Generator&) = delete;
    
    	Generator(Generator&& oth) noexcept : coro(oth.coro)
    	{
    		oth.coro = nullptr;
    	}
    
    	Generator& operator =(Generator&& oth) noexcept
    	{
    		coro = oth.coro;
    		oth.coro = nullptr;
    		return *this;
    	}
    
    	T getValue()
    	{
    		return coro.promise().current_value;
    	}
    
    	bool next()
    	{
    		coro.resume();
    		return !coro.done();
    	}
    
    	struct promise_type
    	{
    		promise_type() = default;
    		~promise_type() = default;
    
    		auto initial_suspend()
    		{
    			return std::suspend_always{};
    		}
    
    		auto final_suspend() noexcept
    		{
    			return std::suspend_always{};
    		}
    
    		auto get_return_object()
    		{
    			return Generator{handle_type::from_promise(*this)};
    		}
    
    		auto return_void()
    		{
    			return std::suspend_never{};
    		}
    
    		auto yield_value(const T value)
    		{
    			current_value = value;
    			return std::suspend_always{};
    		}
    
    		void unhandled_exception()
    		{
    			std::terminate();
    		}
    
    		T current_value;
    	};
    };
    
    Generator<int> getNext(int start = 0, int step = 1)
    {
    	auto value = start;
    	while (true)
    	{
    		co_yield value;
    		value += step;
    	}
    }
    
    int main(int argc, char* argv[])
    {
    	auto gen = getNext();
    	for (int i = 0; i <= 10; ++i)
    	{
    		gen.next();
    		std::cout << std::format("gen value: {}\n", gen.getValue());
    	}
    
    	std::cout << "\n\n";
    
    	auto gen2 = getNext(100, -10);
    	for (int i = 0; i <= 20; ++i)
    	{
    		gen2.next();
    		std::cout << std::format("gen2 value: {}\n", gen2.getValue());
    	}
    }
    

    image-20220928214526409

    看一下执行流程;

    • 创建promise_type
    • 调用get_return_object(),将其结果保存在局部变量
    • 创建generator
    • 调用initial_suspend挂起协程
    • 请求下一个值并消耗一个值然后挂起
    • 接着调用gen.next重复循环
  • 相关阅读:
    awk入门教程
    基于Python的ArcGIS流程化数据处理及应用开发
    MySQL LIKE BINARY 和 LIKE 模糊查询
    Java基于SpringBoot的社区医院管理服务
    C/C++内存管理
    模板方法模式
    5--OpenCV:图形绘制与文字输出
    CSS 轮廓(outline)
    Webpack4从入门到精通以及和webpack5对比_webpack现在用的是哪个版本
    【图像修复】基于改进的Criminisi算法实现图像修复附matlab代码
  • 原文地址:https://blog.csdn.net/qq_42896106/article/details/127098424