co_await(下)

有栈协程和无栈协程

有栈协程和无栈协程
实现一个协程的关键点在于如何保存、恢复和切换上下文,而这也是有栈协程和无栈协程的主要区别。有栈协程通过直接切换堆栈来实现,其构造了一个内存中的栈,而无栈协程使用状态机+按需分配的方式,为所用到的变量开设临时内存,但是实现逻辑更加复杂。

两者各有利弊,只能说无栈的内存利用率更高,而不能说无栈就一定比有栈好。

避免内存分配

异步操作,需要有一个地方用来存储一些对当前操作进度的跟踪,此状态需要一直保持,直到操作完成后才能释放。这一点很好理解,孩子出门读大学,总得知道家在哪儿,还要回家的。

例子-一个简易的线程同步的例子

关于如何使用协程实现一个简单得到线程同步,以下是一个原文中的例子,异步手动重置事件。

此事件的基本要求是,它需要被多个并发执行的协程等待,等待时需要挂起等待的协程,直到某个线程调用.set()方法,此时任何等待的协程都被恢复。如果某个线程已经调用了.set(),那么协程应该继续而不挂起。

消费者生产者模型
T value;
async_manual_reset_event event;// A single call to produce a value
void producer()
{value = some_long_running_computation();// Publish the value by setting the event.event.set();
}// Supports multiple concurrent consumers
task<> consumer()
{// Wait until the event is signalled by call to event.set()// in the producer() function.co_await event;// Now it's safe to consume 'value'// This is guaranteed to 'happen after' assignment to 'value'std::cout << value << std::endl;
}

上述的例子是典型的生产者-消费者模型的异步实现,消费者需要保证在等待生产者完成生产,也就是完成value = some_long_running_computation();这一步操作前,不能消费value,需要确保一个顺序问题。

正如上文所述,无栈协程需要状态机来记录当前状态,比如这个event就有两种状态,setnot set。当处于set状态时,没有正在等待的协程,co_await操作可以继续进行而非挂起。当处于not set的状态时,将会有一批正在等待的协程,等着变成set状态,这一批协程的数量可能为0。

该状态可以用一个简单的std::atomic<void*>来代表。

我们可以通过在coroutine frame中通过存储awaiter对象来避免在堆上进行额外存储。下面这个例子将说明如何实现:

简单的接口
class async_manual_reset_event
{
public:async_manual_reset_event(bool initiallySet = false) noexcept;// No copying/movingasync_manual_reset_event(const async_manual_reset_event&) = delete;async_manual_reset_event(async_manual_reset_event&&) = delete;async_manual_reset_event& operator=(const async_manual_reset_event&) = delete;async_manual_reset_event& operator=(async_manual_reset_event&&) = delete;bool is_set() const noexcept;struct awaiter;awaiter operator co_await() const noexcept;void set() noexcept;void reset() noexcept;private:friend struct awaiter;// - 'this' => set state// - otherwise => not set, head of linked list of awaiter*.mutable std::atomic<void*> m_state;};

显然,这个接口足够直接且简单,但是这里还需要注意的是,此处尚未定义awaiter,接下来就来定义awaiter结构体。

定义awaiter
  • 需要通过初始化来确定哪一个async_manual_reset_event对象会被等待。
  • 需要以链表的形式来存储awaiter的值。
  • 需要存储正在等待的协程的coroutine handle,因此这个事件可以当他切换到‘set’状态时可以恢复协程。
  • 需要可以应用Awaiter的接口,因此其需要三个特别的方法:await_ready,await_suspend,await_resume
    一旦我们将这些全都放到一起,基本的接口如下所示:
struct async_manual_reset_event::awaiter
{awaiter(const async_manual_reset_event& event) noexcept: m_event(event){}bool await_ready() const noexcept;bool await_suspend(std::experimental::coroutine_handle<> awaitingCoroutine) noexcept;void await_resume() noexcept {}private:const async_manual_reset_event& m_event;std::experimental::coroutine_handle<> m_awaitingCoroutine;awaiter* m_next;
};

由于协程是否暂停取决于event的状态是否为set,所以还需要进行判断:

bool async_manual_reset_event::awaiter::await_ready() const noexcept
{return m_event.is_set();
}
await_suspend

await_suspend才是awaitable类型中最重要的部分,这一节主要讲述其用法和所需要注意的部分。

  1. m_awaitingCoroutine 中暂存正在等待的协程的协程句柄,以供恢复操作。
bool async_manual_reset_event::awaiter::await_suspend(std::experimental::coroutine_handle<> awaitingCoroutine) noexcept
{// Special m_state value that indicates the event is in the 'set' state.const void* const setState = &m_event;// Remember the handle of the awaiting coroutine.m_awaitingCoroutine = awaitingCoroutine;// Try to atomically push this awaiter onto the front of the list.void* oldValue = m_event.m_state.load(std::memory_order_acquire);do{// Resume immediately if already in 'set' state.if (oldValue == setState) return false; // Update linked list to point at current head.m_next = static_cast<awaiter*>(oldValue);// Finally, try to swap the old list head, inserting this awaiter// as the new list head.} while (!m_event.m_state.compare_exchange_weak(oldValue,this,std::memory_order_release,std::memory_order_acquire));// Successfully enqueued. Remain suspended.return true;
}

注意,我们需要在加载旧状态的时候去获取内存顺序,

event类的剩余部分

既然我们已经定义完了awaiter类型,让我们重新看一看async_manual_reset_event的应用。

  1. 构造。需要进行初始化,‘not set’(nullptr)或者‘set’(this)
async_manual_reset_event::async_manual_reset_event(bool initiallySet) noexcept
: m_state(initiallySet ? this : nullptr)
{}
  1. is_set(),判断是否为‘set’
bool async_manual_reset_event::is_set() const noexcept
{return m_state.load(std::memory_order_acquire) == this;
}
  1. reset(), 如果是‘set’,则切换到‘not set’状态,反之则置之不理
void async_manual_reset_event::reset() noexcept
{void* oldValue = this;m_state.compare_exchange_strong(oldValue, nullptr, std::memory_order_acquire);
}
  1. set()希望通过交换当前状态和特殊的“set”值this来转换到“set”状态,然后检查旧值是什么。如果有任何等待的协程,那么我们希望在返回之前依次恢复每个协程。
void async_manual_reset_event::set() noexcept
{// Needs to be 'release' so that subsequent 'co_await' has// visibility of our prior writes.// Needs to be 'acquire' so that we have visibility of prior// writes by awaiting coroutines.void* oldValue = m_state.exchange(this, std::memory_order_acq_rel);if (oldValue != this){// Wasn't already in 'set' state.// Treat old value as head of a linked-list of waiters// which we have now acquired and need to resume.auto* waiters = static_cast<awaiter*>(oldValue);while (waiters != nullptr){// Read m_next before resuming the coroutine as resuming// the coroutine will likely destroy the awaiter object.auto* next = waiters->m_next;waiters->m_awaitingCoroutine.resume();waiters = next;}}
}
  1. 最后一步,应用co_await()操作。只需要构造一个awaiter对象。
async_manual_reset_event::awaiter
async_manual_reset_event::operator co_await() const noexcept
{return awaiter{ *this };
}

综上,我们现在获得了一个可等待的异步手动重置事件并且是lock-free,memory-allocation-free的应用。

这文章有点长,感觉理解还有所欠缺,回头会继续精进的,感谢阅读~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/736317.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sqlite 损坏 修复

步骤1 SQLite Download Page下载sqlite3 对应的系统版本 2.参考怎么恢复sqlite 数据库文件✅ - 有乐数据恢复网 sqlite3 dbname > .mode insert > .output dbdump.sql > .dump > .exit 恢复方法1 1.创建一个新的数据库 例如名字叫 test.db 2sqlite3 test.…

Rust 泛型使用过程中的 <T> 和 ::<T> 的区别

Rust 的泛型语法中&#xff0c;<T> 和 ::<T> 有不同的用途和上下文&#xff0c;但它们都与泛型有关。 <T> 在类型定义中 当你在定义函数、结构体、枚举或其他类型时&#xff0c;使用 <T> 来表示泛型参数。例如&#xff1a; fn identity<T>(x:…

将props展平传到DOM上(二)

当我们将展平&#xff08;传播&#xff09;的属性设置子组件时&#xff0c;我们会引入风险&#xff0c;因为我们可能会往 HTML 标签上添加它不支持的属性。 坏实践 下面这个例子会在 DOM 元素上增加一个该元素本身不支持的属性flag。 const Sample () > (<Spread flag…

k8s中的PV和PVC存储介绍

目录 一.PV介绍 1.含义 2.关键配置参数 二.PVC介绍 1.含义 2.关键参数配置 三.PV和PVC的生命周期问题 1.PV的生命周期会有4个阶段 2.用户申请空间PV的周期流程 3.PV和PVC的使用/释放/回收 四.案例演示 1.NFS配置 2.新建PV 3.新建PVC 4.新建Pod测试 5.模拟删除P…

极狐GitLab 16.1 重点功能解读,更好的 DevOps 体验如约而至【二】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 极狐GitLab 在去年 6 月份发布了 16.1 版本。此次发布带来了全…

Linux虚拟机安装Redis

官网下载压缩包&#xff1a;官网链接&#xff0c;然后将对应的tar.gz压缩包放入虚拟机下的/opt目录下。由于redis是C语言开发的&#xff0c;因此需要安装gcc编译器来编译代码&#xff0c;我们下载的压缩包里面是源代码&#xff0c;需要编译。通过yum install gcc指令下载C语言的…

微信小程序使用 iconfont

base64 形式引入 首先我们点击 iconfont 项目中的 项目设置 按钮&#xff0c;位置如下图所示&#xff1a; 我们勾选图中所示三种字体格式&#xff0c;选择 base64 是为了将另外两种字体转为 base64 形式&#xff0c;而选择 woff 与 ttf 字体原因如下&#xff1a; TTF 兼容性更…

Django高级之-cookie-session-token

Django高级之-cookie-session-token 发展史 1、很久很久以前&#xff0c;Web 基本上就是文档的浏览而已&#xff0c; 既然是浏览&#xff0c;作为服务器&#xff0c; 不需要记录谁在某一段时间里都浏览了什么文档&#xff0c;每次请求都是一个新的HTTP协议&#xff0c; 就是请…

大数据 - Spark系列《十二》- 名词术语理解

Spark系列文章&#xff1a; 大数据 - Spark系列《一》- 从Hadoop到Spark&#xff1a;大数据计算引擎的演进-CSDN博客 大数据 - Spark系列《二》- 关于Spark在Idea中的一些常用配置-CSDN博客 大数据 - Spark系列《三》- 加载各种数据源创建RDD-CSDN博客 大数据 - Spark系列《…

redis介绍和使用

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据结构存储系统&#xff0c;它以键-值对&#xff08;key-value&#xff09;的形式存储数据。Redis支持多种数据结构&#xff0c;包括字符串、哈希表、列表、集合、有序集合等。Redis的特点是高性能、高…

C语言游戏实战(11):贪吃蛇大作战(多人对战)

前言&#xff1a; 这款贪吃蛇大作战是一款多人游戏&#xff0c;玩家需要控制一条蛇在地图上移动&#xff0c;吞噬其他蛇或者食物来增大自己的蛇身长度和宽度。本游戏使用C语言和easyx图形库编写&#xff0c;旨在帮助初学者了解游戏开发的基本概念和技巧。 在开始编写代码之前…

是否可以在HTTP中缓存POST方法

如果您想知道是否可以缓存post请求&#xff0c;并尝试研究该问题的答案&#xff0c;那么您很可能不会成功。当搜索“缓存post请求”时&#xff0c;第一个结果是这个StackOverflow问题。 答案是令人困惑的&#xff0c;包括缓存应该如何工作&#xff0c;缓存如何根据RFC工作&…

RDD算子介绍(二)

1. coalesce 用于缩减分区&#xff0c;减少分区个数&#xff0c;减少任务调度成本。 val rdd : RDD[Int] sc.makeRDD(List(1, 2, 3, 4), 4) val newRDD rdd.coalesce(2) newRDD.saveAsTextFile("output") 分区数可以减少&#xff0c;但是减少后的分区里的数据分布…

代码随想录算法训练营Day41 ||leetCode 0-1背包问题 || 416. 分割等和子集

0-1背包问题 dp[i][j]的含义&#xff1a;从下标为[0-i]的物品里任意取&#xff0c;放进容量为j的背包&#xff0c;价值总和最大是多少。 那么可以有两个方向推出来dp[i][j]&#xff0c; 不放物品i&#xff1a;由dp[i - 1][j]推出&#xff0c;即背包容量为j&#xff0c;里面不放…

02-app端文章查看,静态化freemarker,分布式文件系统minIO-黑马头条

app端文章查看&#xff0c;静态化freemarker,分布式文件系统minIO 1)文章列表加载 1.1)需求分析 文章布局展示 1.2)表结构分析 ap_article 文章基本信息表 ap_article_config 文章配置表 ap_article_content 文章内容表 三张表关系分析 1.3)导入文章数据库 1.3.1)导入数据…

详解@Configuration

简介 Configuration注解用于标识一个类作为Spring的配置类&#xff0c;从而允许使用纯Java代码的方式来定义Bean和Bean之间的依赖关系。 Configuration注解是Spring框架中非常核心的一个功能&#xff0c;它提供了一种替代XML配置文件的方法。通过该注解&#xff0c;开发人员可…

ROS2从入门到精通0-2:ROS2简介、对比ROS1与详细安装流程

目录 0 专栏介绍1 什么是机器人操作系统&#xff1f;2 ROS的发展历程3 ROS2与ROS1的区别4 ROS2安装4.1 基本安装4.2 测试ROS24.2.1 测试一&#xff1a;发布者与订阅者4.2.2 测试二&#xff1a;海龟仿真器 5 常见问题 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌…

信息系统项目管理师--成本管理

项⽬成本管理重点关注完成项⽬活动所需资源的成本&#xff0c;但同时也考虑项⽬决策对项⽬产品、服务或成果的使⽤成本、维护成本和⽀持成本的影响。不同的⼲系⼈会在不同的时间&#xff0c;⽤不同的⽅法 测算项⽬成本。 就某些项⽬&#xff0c;特别是⼩项⽬⽽⾔&#xff0c;成…

FocusVisualStyle通常是键盘焦点样式

设置了Button的FocusVisualSytle但是死活没有效果&#xff0c;查了一下这个是键盘焦点样式&#xff0c;摁下Tab键了才能让Button有焦点 <ButtonWidth"100"Height"30"FocusVisualStyle"{DynamicResource MyFocusVisual}">按钮1 </Butto…

VSCode报错:/bin/sh: python: command not found

背景 以前都是直接用txt写python&#xff0c;然后直接命令行运行。 这次涉及的代码较多&#xff0c;决定用编译器。 写好的一段python点击运行报错&#xff01; 问题描述 因为我本地安装的是python3&#xff0c;但是vscode用的是另一个路径的python&#xff0c;所以找不到 解决…