【问题标题】:C++20 coroutines using final_suspend for continuations使用 final_suspend 进行延续的 C++20 协程
【发布时间】: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_readyawait_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 一样)。

问题

  1. 在这种情况下,您打算如何在没有锁的情况下向 await_suspend 添加工作队列?正在寻找可扩展的解决方案。
  2. 为什么底层协程实现不检查有效句​​柄。

导致崩溃的一个人为示例是here

解决方案

  1. 使用一个虚拟任务,它是 co_yield 的无限循环。这是一种浪费的周期,我不希望这样做,而且我需要为每个执行线程创建单独的虚拟任务句柄,这看起来很愚蠢。

  2. 创建 std::coroutine_handle 的特化,其中 resume 什么都不做,返回该句柄的实例。我不想专门化标准库。这也不起作用,因为 coroutine_handle 没有 done()resume() 作为虚拟。

  3. EDIT 1 16/03/2020 调用 continuation() 以原子方式检索延续并将结果存储在 final_awaitable 结构 await_ready 如果没有可用的延续,世界返回真。如果有可用的延续,await_ready 将返回 false,然后将调用 await_suspend 并返回延续(立即恢复它)。 这不起作用,因为任务返回的值存储在协程框架中,如果仍然需要该值,则不得破坏协程框架。在这种情况下,它在 final_awaitable 上调用 await_resume 后被销毁。 仅当任务是连续链中的最后一个时,这才是一个问题。

  4. 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


【解决方案1】:

您可以使用std::noop_coroutine 作为空白继续。

【讨论】:

  • C++20 已经实现了吗?
  • 是的,它做到了!请参阅N4860 中的 17.12.4 无操作协程 [coroutine.noop]
  • Neato!谢谢,我会试试这个我会搁置项目的那部分,这样我就可以从它停止的地方继续:D 如果它成功了,我会将此标记为正确答案。
【解决方案2】:

怎么样:(实际上只是一个大评论。)

struct final_awaitable
{
    bool await_ready() const noexcept
    {
        return false;
    }
    bool await_suspend( promise_handle_t h ) const noexcept
    { 
        auto continuation = h.promise().atomically_pop_a_continuation();
        if (continuation)
           continuation.handle().resume();
        return true;//or whatever is meaningfull for your case.
    }
    void await_resume() const noexcept {}
};

【讨论】:

  • 这是一个很好的解决方案,但是我更喜欢 coroutine_handle 形式的原因是它不会随着每个延续而增加堆栈。
  • @DavidLedger 如果延续结束于调用一个延续而结束于调用另一个延续......所有协程的状态都不会被破坏,因此堆可以在堆栈之前被填充。我想创建一个调用协程、等待承诺、销毁协程然后调用延续的普通函数会更简单。
  • 是的,通常可用的堆比堆栈多,所以这是我更喜欢可扩展性的方法。
猜你喜欢
  • 2021-09-17
  • 2012-02-19
  • 1970-01-01
  • 2021-04-14
  • 2017-09-16
  • 2019-02-12
  • 2020-01-22
相关资源
最近更新 更多