Jas*_*n R 53 c++ concurrency coroutine boost-coroutine
背景:
我问这个是因为我目前有一个包含许多(数百到数千)个线程的应用程序.这些线程中的大多数在很长一段时间内处于空闲状态,等待将工作项放入队列中.当工作项可用时,然后通过调用一些任意复杂的现有代码来处理它.在某些操作系统配置中,应用程序会遇到控制最大用户进程数的内核参数,因此我想尝试减少工作线程数的方法.
我建议的解决方案
它似乎是一个基于协程的方法,我用协程替换每个工作线程,将有助于实现这一目标.然后,我可以拥有一个由实际(内核)工作线程池支持的工作队列.当项目被放置在特定协程的队列中进行处理时,会将一个条目放入线程池的队列中.然后它将恢复相应的协同程序,处理其排队的数据,然后再次暂停它,释放工作线程以执行其他工作.
实施细节:
在考虑如何做到这一点时,我无法理解无堆栈和堆栈协程之间的功能差异.我有一些使用Boost.Coroutine库的堆栈协同程序的经验.我发现从概念层面理解它相对容易:对于每个协同程序,它维护CPU上下文和堆栈的副本,当你切换到协程时,它切换到保存的上下文(就像内核模式调度程序一样) ).
对我来说不太清楚的是无堆栈协程与此有何不同.在我的应用程序中,与上述工作项排队相关的开销量非常重要.我见过的大多数实现,比如新的CO2库,都表明无堆栈协程提供了更低开销的上下文切换.
因此,我想更清楚地理解无堆栈和堆栈协程之间的功能差异.具体来说,我想到了这些问题:
像这样的参考文献表明,区别在于你可以在一个堆栈与无堆栈协程中产生/恢复.是这样的吗?有一个简单的例子,我可以在一个堆栈的协程中做但不能在无堆栈的协同程序中吗?
使用自动存储变量(即"堆栈中的变量")是否有任何限制?
我可以从无堆协程中调用哪些函数有任何限制吗?
如果无堆栈协程没有保存堆栈上下文,那么当协同程序运行时,自动存储变量会去哪里?
Jam*_*ree 49
首先,谢谢你看看二氧化碳 :)
Boost.Coroutine 文档很好地描述了堆栈协程的优点:
stackfulness
与无堆栈协程相反,可以在嵌套的堆栈帧内挂起堆栈协程.执行恢复在之前暂停的代码中的完全相同的点.使用无堆栈协程,只能暂停顶级例程.该顶级例程调用的任何例程本身可能不会暂停.这禁止在通用库中的例程中提供暂停/恢复操作.
一流的延续
第一类延续可以作为参数传递,由函数返回并存储在稍后要使用的数据结构中.在一些实现中(例如C#yield),不能直接访问或直接操纵延续.
如果没有堆栈和第一类语义,则不能支持一些有用的执行控制流(例如,协作式多任务处理或检查点).
这对你来说代表着什么?例如,假设您有一个访问者的功能:
template<class Visitor>
void f(Visitor& v);
Run Code Online (Sandbox Code Playgroud)
你想将它转换为迭代器,使用堆栈协程,你可以:
asymmetric_coroutine<T>::pull_type pull_from([](asymmetric_coroutine<T>::push_type& yield)
{
f(yield);
});
Run Code Online (Sandbox Code Playgroud)
但是使用无堆栈协程,没有办法这样做:
generator<T> pull_from()
{
// yield can only be used here, cannot pass to f
f(???);
}
Run Code Online (Sandbox Code Playgroud)
一般来说,堆栈协程比无堆协程更强大.那么为什么我们要无堆叠协程呢?简答:效率.
堆栈协程通常需要分配一定量的内存来容纳其运行时堆栈(必须足够大),并且与无堆栈协议相比,上下文切换更昂贵,例如Boost.Coroutine需要40个周期,而CO2只需7个在我的机器上平均循环,因为无堆栈协程需要恢复的唯一东西是程序计数器.
也就是说,在语言支持的情况下,只要协程中没有递归,堆栈协程也可以利用编译器计算的最大大小,因此内存使用也可以得到改善.
说到无堆栈协程,请记住它并不意味着根本没有运行时堆栈,它只意味着它使用与主机端相同的运行时堆栈,因此您也可以调用递归函数,只需所有递归都将在主机的运行时堆栈上发生.相反,对于堆栈协程,当您调用递归函数时,递归将在协程自己的堆栈上发生.
回答问题:
不,这是二氧化碳的模拟限制.通过语言支持,协程可见的自动存储变量将被放置在协同程序的内部存储上.注意我强调"coroutine可见",如果协同程序调用一个在内部使用自动存储变量的函数,那么这些变量将放在运行时堆栈上.更具体地说,无堆栈协程只需要保留恢复后可以使用的变量/临时值.
需要说明的是,您也可以在CO2的协同体中使用自动存储变量:
auto f() CO2_RET(co2::task<>, ())
{
int a = 1; // not ok
CO2_AWAIT(co2::suspend_always{});
{
int b = 2; // ok
doSomething(b);
}
CO2_AWAIT(co2::suspend_always{});
int c = 3; // ok
doSomething(c);
} CO2_END
Run Code Online (Sandbox Code Playgroud)
只要定义不在任何前面await
.
没有.
上面回答,无堆栈协程不关心被调用函数中使用的自动存储变量,它们只是放在正常的运行时堆栈上.
如果您有任何疑问,只需查看二氧化碳的源代码,它可以帮助您了解引擎盖下的机制;)
您想要的是用户态线程/纤程 - 通常您希望将代码(在纤程中运行)挂起在深度嵌套的调用堆栈中(例如解析来自 TCP 连接的消息)。在这种情况下,您不能使用无堆栈上下文切换(应用程序堆栈在无堆栈协程之间共享 - >被调用子例程的堆栈帧将被覆盖)。
您可以使用类似 boost. Fiber 的东西,它基于 boost.context 实现用户态线程/纤维。