这个问题不是关于C++20中的协同问题,而是一般的协同问题。
这几天我在学C++20的协同运算。 我已经学习了堆叠和无堆叠的协同产品介绍。 我也希望得到更多的信息。
以下是我对无堆叠协同功能的理解:
>
无堆栈协同程序在运行时在调用方的堆栈上有堆栈。
当它自己挂起时,由于无堆栈的coroutines只能在顶层函数处挂起,它的堆栈是可预测的,有用的数据存储在某个区域。
当它不运行时,它没有堆栈。 它绑定了一个句柄,客户端可以通过句柄恢复协同。
协同TS指定在为协同帧分配存储时调用非数组运算符new
。 不过,我认为这是不必要的,所以才提出质询。
一些解释/考虑:
>
将协同项的状态放在哪里? 在句柄中,该句柄原来存储指针。
动态分配并不意味着存储在堆上。 但是我的目的是要省略对operatornew
的调用,无论它是如何实现的。
来自CPPreference:
在下列情况下,可以优化对运算符new的调用(即使使用自定义分配器
>
协同状态的生存期严格嵌套在调用方的生存期内,并且
协同帧的大小在呼叫站点是已知的
对于第一个要求,如果协同项比调用者的寿命长,则将状态直接存储在句柄中仍然是可以的。
对于另一个,如果调用方不知道大小,那么它如何编写参数来调用运算符new
呢? 实际上,我甚至无法想象在哪种情况下打电话的人不知道尺寸。
根据这个问题,Rust似乎有一个不同的实现方式。
考虑这个假设情况:
void foo(int);
task coroutine() {
int a[100] {};
int * p = a;
while (true) {
co_await awaitable{};
foo (*p);
}
}
p
指向a
的第一个元素,如果在两次恢复之间,a
的内存位置发生了变化,则p
将无法保存正确的地址。
函数堆栈的内存必须以这样一种方式分配,即在挂起和随后的恢复之间保持内存。 但是如果某些对象引用了这个内存中的对象(或者至少在不增加复杂性的情况下不能这样做),这个内存就不能被移动或复制。 这就是为什么编译器有时需要在堆上分配这个内存的原因。
无堆栈的协同程序在运行时在调用方的堆栈上有堆栈。
这就是你误解的根源。
基于连续的协同(这就是“无堆栈协同”的含义)是一种协同机制,它被设计为能够提供协同到一些其他代码,这些代码将在某个异步进程完成后继续执行。 这种恢复可能发生在其他一些线程中。
因此,不能假定堆栈“在调用方的堆栈上”,因为调用方和调度协同恢复的进程不一定在同一线程中。 coroutine需要能够比调用方的时间更长,因此coroutine的堆栈不能在调用方的堆栈上(通常,在某些co_yield
样式的情况下,它可以在)。
coroutine句柄表示coroutine的堆栈。 只要该句柄存在,协同句柄的堆栈也存在。
当它不运行时,它没有堆栈。 它绑定了一个句柄,客户端可以通过句柄恢复协同。
这个“句柄”是如何存储协同关系的所有局部变量的呢? 很明显,它们被保存了(如果它们没有保存,这将是一个糟糕的协同机制),因此它们必须被存储在某个地方。 函数的局部变量所在位置的名称称为“堆栈”。
称它为“把手”并不能改变它是什么。
但是我的目的是要省略对operatornew
的调用,无论它是如何实现的。
嗯。。。 你不能。 如果从不调用new
是编写您正在编写的任何软件的一个重要组件,那么您就不能使用co_await
样式的协同延续。 没有一组规则可以用来保证在协同项中删除new
。 如果您使用的是一个特定的编译器,您可以做一些测试,看看它删除了什么,没有删除什么,但仅此而已。
你所引用的规则仅仅是让你有可能回避呼叫的情况。
对于另一个,如果调用方不知道大小,那么它如何编写参数来调用运算符new
呢?
记住:C++中的co_await
协同关系实际上是函数的一个实现细节。 调用方不知道它调用的任何函数是否是协同函数。 从外部看,所有的协同函数都像常规函数。
创建协同堆栈的代码发生在函数调用内,而不是函数调用外。