【发布时间】:2020-10-23 13:45:29
【问题描述】:
首先我想预测我的代码的内存使用情况,就像任何负责任的程序员应该做的那样。即使我 不 决定使用 placement new 分配我的协程帧,这也适用,就像我一样(见下面的伪代码)。 即使我改变了关于放置新所有协程的想法,因此我让编译器在堆上分配我的所有协程,我仍然希望 C++ 语言告诉我我要堆多少吃饱了。
但是,IRL,我的目标是高可靠性和嵌入式环境。甚至可能没有堆,所以...
struct coroutine_return_type
{
struct promise_type
{
void *operator new(std::size_t sz, char *buf, std::size_t szbuf)
{
if (sz > szbuf)
throw std::bad_alloc{};
return buf;
}
void operator delete(void *)
{
}
// ...
};
// ...
};
coroutine_return_type my_coroutine(char *, std::size_t)
{
// The arguments, char * and std::size_t,
// have been fowrarded to promise_type::operator new
// but here in the coroutine body they aren't used again...
for ( ; ; )
co_yield /* something */;
}
struct coroutine_instance_type
{
char my_coroutine_frame[ /* WHAT? */ ];
coroutine_return_type my_coroutine_instance;
coroutine_instance_type()
: my_coroutine_instance{my_coroutine(my_coroutine_frame, sizeof(my_coroutine_frame))}
{
// ...
}
// ...
};
我想要什么
我想要一个编译时表达式来返回我的协程大小的上限,以替换 /* WHAT? */。
愚蠢的解决方案
有一种明显愚蠢的方法可以(不完全)做我想做的事:
-
子类 std::bad_alloc。然后我的
operator new中的throw std::bad_alloc{}变为throw std::my_bad_alloc{sz}。 catch 块可以调用my_bad_alloc_instance.get_parameter()来了解sz在operator new中的内容。 -
调用
my_coroutine(nullptr, 0)并捕获异常。
这有什么愚蠢的(非详尽列表):
它不是编译时表达式,因为它必须使用throw“返回”它的值,而throw 永远不能在编译时表达式中使用。但是在我的伪代码中替换 /* WHAT? */ 需要是编译时表达式。
这是一个样本,而不是上限。假设协程框架的实际分配大小取决于运行时的条件。 (现在,我不希望在我的 IRL 应用程序中实际出现针对不同运行时条件的不同协程大小,但根据 C++ 标准,这似乎是可能的。)在这种情况下,仅了解实际传递给operator new 的大小是不够的。相反,所需的表达式必须返回一个上限,关于可以传递给operator new。
所以,总结一下:
问题摘要
C++ 语言提供了哪些工具来查询协程帧的大小?理想的工具应该是用于为协程分配非堆内存的编译时表达式,或者,同样的工具也可以用于限制堆的数量。
【问题讨论】:
-
"我的目标是高可靠性和嵌入式环境。甚至可能没有堆" 你确定要使用
co_await风格的协程吗在这样的环境中?如果每个字节和周期都那么宝贵,我会避免性能特征不确定的 C++ 机制。就像您要避免使用dynamic_cast、typeid等一样。 -
@NicolBolas 因为
co_await胜过std::thread。 -
@cs-: ...什么?
co_await协程只不过是一种暂停和恢复函数执行的机制。它们本质上与线程无关。现在,它们的主要设计目的是促进异步延续的使用。但是延续从假设一个线程已经存在并且将要做某事开始,所以你希望这个函数在那个线程中执行的事情完成之后执行。它们不能替代std::thread或任何其他线程创建机制。 -
@NicolBolas 想一想,“我有 N 项家务要做。当一项家务为下一个事件而阻塞时,另一项家务应该一直运行到所有家务都阻塞为止。”如果你有第三种方式,那么你今天将成为我的英雄,在(第一种方式)为每个杂务生成一个线程并让操作系统调度它们并(第二种方式)保持在一个线程上但将每个实现为可连续的函数和调度之后他们自己。
-
@cs-:你所描述的几乎不需要给每个“家务”赋予它自己的线程。您所说的是可恢复的任务和将它们移交给不同线程的管理器。虽然
co_await使此类事情(更)易于编码和推理,但可恢复任务并行性并不是一个新的研究领域。传统方法往往涉及显式延续函数或成熟的纤程(比任何co_await协程的堆栈大得多)。co_await非常适合您的需求,但您必须接受随之而来的缺乏控制。
标签: c++ c++20 c++-coroutine