【发布时间】:2020-06-26 01:34:09
【问题描述】:
背景
在确信C++ stackless coroutines are pretty awesome之后。我一直在为我的代码库实现协程,并在 final_suspend 中发现了一个奇怪的地方。
上下文
假设您有以下 final_suspend 函数:
final_awaitable final_suspend() noexcept
{
return {};
}
并且,final_awaitable的实现方式如下:
struct final_awaitable
{
bool await_ready() const noexcept
{
return false;
}
default_handle_t await_suspend( promise_handle_t h ) const noexcept
{
return h.promise().continuation();
}
void await_resume() const noexcept {}
};
如果此处的延续是从任务队列中以原子方式检索到的并且,则任务队列可能为空(这可能发生在 await_ready 和 await_suspend 之间的任何时间) 那么 await_suspend 必须能够返回一个空白的延续。
据我了解,当 await_suspend 返回句柄时,返回的句柄会立即恢复(N4775 草稿中的 5.1)。因此,如果这里没有可用的延续,任何应用程序都会崩溃,因为在从 await_suspend 接收到无效协程句柄后调用了一个无效的协程句柄。
以下是执行顺序:
final_suspend Constructs final_awaitable.
final_awaitable::await_ready Returns false, triggering await_suspend.
final_awaitable::await_suspend Returns a continuation (or empty continuation).
continuation::resume This could be null if a retrieved from an empty work queue.
似乎没有为有效句柄指定检查(就像 await_suspend 返回 bool 一样)。
问题
- 在这种情况下,您打算如何在没有锁的情况下向 await_suspend 添加工作队列?正在寻找可扩展的解决方案。
- 为什么底层协程实现不检查有效句柄。
导致崩溃的一个人为示例是here。
解决方案
使用一个虚拟任务,它是 co_yield 的无限循环。这是一种浪费的周期,我不希望这样做,而且我需要为每个执行线程创建单独的虚拟任务句柄,这看起来很愚蠢。
-
创建 std::coroutine_handle 的特化,其中 resume 什么都不做,返回该句柄的实例。我不想专门化标准库。这也不起作用,因为 coroutine_handle 没有 done() 和 resume() 作为虚拟。
EDIT 1 16/03/2020 调用 continuation() 以原子方式检索延续并将结果存储在 final_awaitable 结构 await_ready 如果没有可用的延续,世界返回真。如果有可用的延续,await_ready 将返回 false,然后将调用 await_suspend 并返回延续(立即恢复它)。 这不起作用,因为任务返回的值存储在协程框架中,如果仍然需要该值,则不得破坏协程框架。在这种情况下,它在 final_awaitable 上调用 await_resume 后被销毁。 仅当任务是连续链中的最后一个时,这才是一个问题。
EDIT 2 - 20/03/2020 忽略从 await_suspend 返回可用协程句柄的可能性。仅从顶级协同例程恢复继续。这看起来效率不高。
01/04/2020
我还没有找到没有明显缺点的解决方案。我想我赶上这个的原因是因为 await_suspend 似乎旨在解决这个确切的问题(能够返回一个 corountine_handle)。我只是无法弄清楚预期的模式。
【问题讨论】:
-
难道不能在
final_awaitable中定义一个bool await_suspend( promise_handle_t h)并且有条件地在这个函数的主体中恢复延续吗? -
可以返回 true 或 false 以有条件地恢复协程,但不能继续。尽管如此,奇怪的是在恢复之前没有检查协程句柄。它似乎在使用 await_ready 恢复任何其他协程之前检查一个标志,但这里没有。也许这只是我的理解差距......我只是不明白当你没有可用的延续并且协程没有准备好(并返回 coroutine_handle)时,你应该如何实际暂停。
标签: c++ c++20 continuations c++-coroutine