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