boost::coroutine2 对比协程TS
boost::coroutine2 vs CoroutineTS
Boost::Coroutine2 和 CoroutineTS(C++20) 是 C++ 中流行的协程实现。两者都会挂起和恢复,但两种实现遵循完全不同的方法。
协程TS(C++20)
- Stackless
- 暂停 return
- 使用特殊关键字
generator<int> Generate()
{
co_yield;
});
boost::coroutine2
- 堆叠
- 调用暂停
- 不要使用特殊关键字
pull_type source([](push_type& sink)
{
sink();
});
是否有任何特定的用例我应该 select 只有其中一个?
主要的技术区别在于您是否希望能够从嵌套调用中退出。这不能使用无堆栈协程来完成。
另一件需要考虑的事情是stackful协程有自己的堆栈和上下文(比如信号掩码、堆栈指针、CPU寄存器等),所以它们有更大的内存占用比无堆栈协程。这可能是一个问题,尤其是当您的系统资源受限或同时存在大量协程时。
我不知道它们在现实世界中如何比较性能,但总的来说,stackless 协程更高效,因为它们的开销更少(stackless 任务切换不必交换堆栈,store/load寄存器,恢复信号掩码等)。
有关最小堆栈协程实现的示例,请参阅 Simon Tatham's coroutines using Duff's Device。很直观的是,它们非常高效。
另外, 有很好的答案,更详细地介绍了堆栈协程和无堆栈协程之间的区别。
如何从无堆栈协程中的嵌套调用中退出?
尽管我说这是不可能的,但这并不是 100% 正确:您可以使用(至少两个)技巧来实现这一点,每个技巧都有一些缺点:
首先,您必须将每个应该能够产生调用协程的调用也转换为协程。现在,有两种方法:
蹦床方法:您只需在循环中从父协程调用子协程,直到它returns。每次你通知子协程,如果它没有完成,你也会让出调用协程。请注意,这种方法禁止直接调用子协程,您总是必须调用最外层的协程,然后它必须重新进入整个调用堆栈。对于嵌套深度 n,这具有 O(n) 的调用和 return 复杂度。如果你正在等待一个事件,事件只需要通知最外层的协程。
parent link方法:你把父协程地址传给子协程,yield父协程,手动yield子协程完成后恢复父协程。请注意,此方法禁止直接调用除最内层协程之外的任何协程。这种方法的调用和 return 复杂度为 O(1),因此通常更可取。缺点是必须在某个地方手动注册最内层的协程,这样下一个想要恢复外层协程的事件才知道直接针对哪个内层协程。
注意: By call and return complexity 我的意思是通知协程恢复它时所采取的步骤数,以及将其通知 return 后再次通知调用通知程序后采取的步骤。
Boost::Coroutine2 和 CoroutineTS(C++20) 是 C++ 中流行的协程实现。两者都会挂起和恢复,但两种实现遵循完全不同的方法。
协程TS(C++20)
- Stackless
- 暂停 return
- 使用特殊关键字
generator<int> Generate()
{
co_yield;
});
boost::coroutine2
- 堆叠
- 调用暂停
- 不要使用特殊关键字
pull_type source([](push_type& sink)
{
sink();
});
是否有任何特定的用例我应该 select 只有其中一个?
主要的技术区别在于您是否希望能够从嵌套调用中退出。这不能使用无堆栈协程来完成。
另一件需要考虑的事情是stackful协程有自己的堆栈和上下文(比如信号掩码、堆栈指针、CPU寄存器等),所以它们有更大的内存占用比无堆栈协程。这可能是一个问题,尤其是当您的系统资源受限或同时存在大量协程时。
我不知道它们在现实世界中如何比较性能,但总的来说,stackless 协程更高效,因为它们的开销更少(stackless 任务切换不必交换堆栈,store/load寄存器,恢复信号掩码等)。
有关最小堆栈协程实现的示例,请参阅 Simon Tatham's coroutines using Duff's Device。很直观的是,它们非常高效。
另外,
如何从无堆栈协程中的嵌套调用中退出? 尽管我说这是不可能的,但这并不是 100% 正确:您可以使用(至少两个)技巧来实现这一点,每个技巧都有一些缺点: 首先,您必须将每个应该能够产生调用协程的调用也转换为协程。现在,有两种方法:
蹦床方法:您只需在循环中从父协程调用子协程,直到它returns。每次你通知子协程,如果它没有完成,你也会让出调用协程。请注意,这种方法禁止直接调用子协程,您总是必须调用最外层的协程,然后它必须重新进入整个调用堆栈。对于嵌套深度 n,这具有 O(n) 的调用和 return 复杂度。如果你正在等待一个事件,事件只需要通知最外层的协程。
parent link方法:你把父协程地址传给子协程,yield父协程,手动yield子协程完成后恢复父协程。请注意,此方法禁止直接调用除最内层协程之外的任何协程。这种方法的调用和 return 复杂度为 O(1),因此通常更可取。缺点是必须在某个地方手动注册最内层的协程,这样下一个想要恢复外层协程的事件才知道直接针对哪个内层协程。
注意: By call and return complexity 我的意思是通知协程恢复它时所采取的步骤数,以及将其通知 return 后再次通知调用通知程序后采取的步骤。