原文
避免分配内存
异步操作一般需要存储一些仅在操作中有的跟踪操作进度
的每操作状态
.
如,调用异步Win32I/O
函数,要分配并传递OVERLAPPED
结构指针.调用者
确保完成操作前它有效.
传统基于回调的API
,要在堆
上分配此状态,以确保它有适当生命期.多个,则要为每个
操作分配和释放
它.
如果有性能问题,则可用自定义分配器
从池中分配
.
但是,使用协程
时,可利用挂起
协程时,协程帧中的局部变量
是活的,从而避免
为操作状态分配
堆存储.
在等待器
对象中保存每操作状态
,可有效从协程帧
中"借用
"内存,以便在协待
式时存储
它.
完成操作
后,恢复协程并析构等待器
对象,从而释放
协程帧中内存.
最终,协程帧
仍可能按堆分配.但是,一旦分配,协程帧就可来仅用单个
堆分配来完成异步
操作.
把协程帧
当作真正高性能的分配
内存器.编译时计算出所有局部变量期望总内存大小
,然后可无成本
按需分配此内存
给局部变量!
示例:实现简单的线程同步原语
异步手动重置
事件.
基本要求是,需要多个并发协程是可等待
的,且等待时要挂起
等待的协程,直到已恢复等待协程
,且某个线程调用.set()
方法.如果某个线程已调用了.set()
,则应该继续而不挂起协程
.
最好,还想使它为无异
,非堆分配且无锁
.示例如下:
T 值;
异步手动重置事件 事件;
//单个调用来生成值
空 生产者()
{值 = 一些长运行计算();//通过设置事件来发布值.事件.置();
}
//支持多个并发用户
任务<> 消费者()
{//在`生产者()`函数中,等待`事件.置()`发出信号.协待 事件;//现在消费"值"是安全的,保证在赋值给"值"后.标::输出 << 值 << 标::行尾;
}
先考虑该事件可能的状态:"未置"和"已置"
.
"未置
"状态时,有个等待置它的等待
协程列表(可能是空的).
为"已置"
状态时,不会有等待
协程,因为协待
此状态事件的协程
可继续运行而不会挂起
.
该状态可用单个std::atomic<void*>
来表示.
1,为"已置"
状态保留特殊指针值
.本例中,使用事件的this
指针,因为知道该指针
不可能与列表项
地址相同.
2,否则,事件为"未置
"状态,且该值是等待协程
结构的单链表指针.
在协程帧中的"等待器"
对象中存储
节点,可避免额外调用
堆上的链表分配节点.
因此,从此类接口开始:
类 异步手动重置事件
{
公:异步手动重置事件(极 初始置 = 假) 无异;//无需复制/移动异步手动重置事件(常 异步手动重置事件&) = 删;异步手动重置事件(异步手动重置事件&&) = 删;异步手动重置事件& 符号=(常 异步手动重置事件&) = 删;异步手动重置事件& 符号=(异步手动重置事件&&) = 删;极 是已置() 常 无异;构 等待器;等待器 符号 协待() 常 无异;空 置() 无异;空 重置() 无异;
私:友 构 等待器;//本为=>已置状态//否则为`=>`未置,`等待器*`链表的头部.可变 标::原子<空*> m状态;
};
在此,有个相当直接和简单
接口.此时要注意它有个返回未定义等待器的协待()
操作符方法.
现在定义等待器
.
定义等待器
首先,要知道它在等待哪个异步手动重置事件
对象,因此它需要事件引用
及构造器来初化
它.
还需要充当等待器
值链接列表中的节点
,因此要有列表中下个
等待器对象的指针
.
还要存储执行协待
式等待协程的协柄
,以便事件
可在协程变为"已置"
时恢复协程.
不必关心协程的承诺
类型是什么,所以只使用协柄<>
.
最后,要实现等待器
接口,因此三个特殊方法:直接协,挂起协
和恢复协
.不想从协待
式返回值,恢复协
因此可返回空
.
放在一起,等待器
的基本接口如下:
构 异步手动重置事件::等待器
{等待器(常 异步手动重置事件& 事件) 无异: m事件(事件){}极 直接协() 常 无异;极 挂起协(标::实验性::协柄<> 等待协程) 无异;空 恢复协() 无异 {}
私:常 异步手动重置事件& m事件;标::实验性::协柄<> m等待协程;等待器* m下个;
};
现在,协待
事件时,如果已置事件
,不想等待
挂起协程.因此,如果已置事件
,可定义直接协()
返回真
.
极 异步手动重置事件::等待器::直接协() 常 无异
{中 m事件.是已置();
}
接着,看看挂起协()
方法.这一般是大多数可等待
类型的神奇
的地方.
首先,它需要存储
等待协程的协程句柄
到m等待协程
成员中,这样事件稍后可对它调用.恢复()
方法.
然后,一旦完成,需要试原子
方式把等待器
排队到等待链
列表中.如果成功加入,则返回true
以指示不想立即恢复协程.否则,如果发现事件已并发
更改为"已置"
状态,则返回假
以指示应立即恢复协程
.
极 异步手动重置事件::等待器::挂起协(标::实验性::协柄<> 等待协程) 无异
{//指示是否`"置"`事件状态的特殊`m状态`值.常 空* 常 置状态 = &m事件;//记住等待协程的句柄.m等待协程 = 等待协程;//试原子方式把此`等待者`推到列表头.空* 旧值 = m事件.m状态.加载(标::获取内存序);干{//如果已在"置"状态,请立即恢复.如 (旧值 == 置状态) 中 假; //更新链表以指向当前头.m下个 = 静转<等待器*>(旧值);//最后,试交换旧的列表头,按新列表头插入该等待器.} 当 (!m事件.m状态.弱比交( 旧值, 本, 标::释放内存序, 标::获取内存序));//已成功入列.保持挂起.中 真;
}
注意,在加载旧状态
时使用"获取
"内存序,以便如果读取特殊的"set"
值时,可查看调用"set()"
前的写入
.
如果成功比较交换
,需要"释放
"语义,以便后续调用"set()"
时,可看到写入m等待协程
和先前写入的协程状态.
填写事件类的其余部分
现在已定义了等待器,再看看异步手动重置事件
方法.
首先是构造器.它要按"未置
"状态(即nullptr
)初化或按"已置
"状态(即this
)初化空的等待列表
.
异步手动重置事件::异步手动重置事件( 极 初始置) 无异
: m状态(初始置?本:空针)
{}
接着,is_set()
方法非常简单,如果有以下特殊值
,则它是"已置"
:
极 异步手动重置事件::是已置() 常 无异
{中 m状态.加载(标::获取内存序) == 本;
}
接着是reset()
方法.如果为"已置",则改为"未置",否则不变.
空 异步手动重置事件::重置() 无异
{空* 旧值 = 本;m状态.强比交(旧值, 空针, 标::获取内存序);
}
set()
方法,通过用特殊的'已置'
值this
,来交换当前状态来过渡
到'已置'
状态,然后检查旧值
.如果有等待
协程,则在返回
前依次恢复
每个协程.
空 异步手动重置事件::置() 无异
{//需要"释放",以便后续的`"协待"`可见之前的写入.需要"获取",以便通过`等待协程`来查看先前的写入.空* 旧值 = m状态.交换(本, 标::内存序取释放);如 (旧值 != 本){//不是"已置"状态.按已取且需要恢复的等待链接列表头取旧值.动* 等待 = 静转<等待器*>(旧值);当 (等待 != 空针){//在恢复协程之前读`m下个`,因为恢复协程可能会析构等待器对象.动* 下个 = 等待->m下个;等待->m等待协程.恢复();等待 = 下个;}}
}
最后,要实现协待()
操作符.这只需要构造一个等待器对象
.
异步手动重置事件::等待器
异步手动重置事件::符号 协待() 常 无异
{中 等待器{ *本 };
}
好了.一个可等待的无锁,无分配内存
,无异
实现的异步
手动重置事件.