【问题标题】:boost::coroutine2 vs CoroutineTSboost::coroutine2 vs CoroutineTS
【发布时间】:2019-07-31 08:51:51
【问题描述】:

Boost::Coroutine2 和 CoroutineTS(C++20) 是 C++ 中流行的协程实现。两者都暂停和恢复,但两种实现遵循完全不同的方法。

CoroutineTS(C++20)

  • 无堆栈
  • 返回暂停
  • 使用特殊关键字
generator<int> Generate()
{
   co_yield;
});

boost::coroutine2

  • 堆叠式
  • 电话暂停
  • 不要使用特殊关键字
pull_type source([](push_type& sink)
{
   sink();
});

是否有任何我应该只选择其中一个的特定用例?

【问题讨论】:

    标签: c++ boost coroutine boost-coroutine2


    【解决方案1】:

    主要的技术区别是您是否希望能够从嵌套调用中产生。使用无堆栈协程无法做到这一点。

    另外需要考虑的是,堆栈式协程有自己的堆栈和上下文(例如信号掩码、堆栈指针、CPU 寄存器等),因此它们比无堆栈式协程具有更大的内存占用。这可能是一个问题,尤其是当您的系统资源受限或同时存在大量协程时。

    我不知道它们在现实世界中如何比较性能,但总的来说,无堆栈协程更有效,因为它们的开销更少(无堆栈任务切换不必交换堆栈、存储/加载寄存器和恢复信号掩码等)。

    有关最小无堆栈协程实现的示例,请参阅使用 Duff's DeviceSimon Tatham's coroutines。很直观,它们尽可能高效。

    另外,this question 有很好的答案,更详细地介绍了堆栈和无堆栈协程之间的区别。

    如何从无堆栈协程中的嵌套调用中产生? 尽管我说不可能,但这并不是 100% 正确的:您可以使用(至少两个)技巧来实现这一点,每个技巧都有一些缺点: 首先,您必须将每个应该能够产生调用协程的调用也转换为协程。现在,有两种方法:

    1. 蹦床方法:您只需在循环中从父协程调用子协程,直到它返回。每次你通知子协程,如果它没有完成,你也会让调用协程。请注意,这种方法禁止直接调用子协程,您始终必须调用最外层的协程,然后必须重新进入整个调用堆栈。对于嵌套深度 n,它的调用和返回复杂度为 O(n)。如果你在等待一个事件,事件只需要通知最外层的协程。

    2. 父链接方法:你将父协程地址传递给子协程,让父协程,子协程完成后手动恢复父协程。请注意,这种方法禁止直接调用除最内层协程之外的任何协程。这种方法的调用和返回复杂度为 O(1),因此通常更可取。缺点是你必须在某处手动注册最里面的协程,以便下一个想要恢复外部协程的事件知道直接针对哪个内部协程。

    注意调用和返回复杂度是指通知协程恢复时所采取的步骤数,以及通知协程返回后所采取的步骤再次调用通知器。

    【讨论】:

    • 如果我想从嵌套调用中产生,那么我必须使用 boost::coroutine2 进行堆叠。如果我创建了数千个协程,它将占用大量内存。 boost::coroutine2 有什么替代方案吗?或者是否有任何有效的堆栈分配方法可以与 boost::coroutine2 一起使用?
    • @NisalDilshan 我对 boost::coroutine2 了解不多,我从未使用过它。但是,我用一个可以使用无堆栈协程模拟堆栈的解决方案更新了我的答案,它应该(对于合理的嵌套深度)比堆栈协程更节省内存。
    • 你知道堆栈式协程的堆栈大小吗?我们是否需要分配一个等于线程堆栈大小的大小? (stackoverflow.com/questions/55137871/…)
    • @NisalDilshan stack_traits::default_size() 函数应该是您正在寻找的。这是默认分配的堆栈大小。您当然可以使用具有不同堆栈大小的自定义堆栈分配器。
    猜你喜欢
    • 1970-01-01
    • 2016-12-23
    • 1970-01-01
    • 2018-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 2020-12-21
    相关资源
    最近更新 更多