c ++-无栈协程与有栈协程有何不同?
背景:
我之所以这么问,是因为我目前有一个具有许多(数百至数千)线程的应用程序。 这些线程中的大多数在大部分时间都处于空闲状态,等待将工作项放入队列中。 当工作项可用时,然后通过调用一些任意复杂的现有代码对其进行处理。 在某些操作系统配置上,应用程序会遇到控制最大用户进程数的内核参数,因此我想尝试减少工作线程数的方法。
我建议的解决方案:
似乎是基于协程的方法,用协程替换每个工作程序线程将有助于实现此目的。 然后,我可以有一个由实际(内核)工作线程池支持的工作队列。 将项目放置在特定协程的队列中进行处理时,会将条目放置在线程池的队列中。 然后它将恢复相应的协程,处理其排队的数据,然后再次将其暂停,以释放工作线程来执行其他工作。
实施细节:
在考虑如何执行此操作时,我很难理解无栈协程和有栈协程之间的功能差异。 我有一些使用Boost.Coroutine库使用堆栈协程的经验。 我发现从概念上理解相对容易:对于每个协程,它维护CPU上下文和堆栈的副本,并且当您切换到协程时,它将切换到该保存的上下文(就像内核模式调度程序那样 )。
我不太清楚的是无栈协程与此有何不同。 在我的应用程序中,与上述工作项排队相关的开销非常重要。 我见过的大多数实现(例如新的CO2库)都建议无堆栈协程提供开销更低的上下文切换。
因此,我想更清楚地了解无栈协程和有栈协程之间的功能差异。 具体来说,我想到了以下问题:
像这样的引用表明,区别在于您可以在有堆栈的协程与无堆栈的协程中屈服/恢复。 是这样吗 是否有一个简单的示例,说明我可以在堆栈式协程中执行某些操作,但不能在无堆栈的协程中执行某些操作?
使用自动存储变量(即“在堆栈上”的变量)是否有任何限制?
我可以从无堆栈协程调用哪些函数有任何限制?
如果没有为无堆栈协程保存堆栈上下文,那么当协程运行时,自动存储变量会移到哪里?
首先,谢谢您对二氧化碳的关注:)
Boost.Coroutine文档很好地描述了堆栈式协程的优势:
堆叠性
与无栈协程相反 可以从嵌套堆栈框架中挂起。 执行恢复在 与之前暂停的代码完全相同。 用 一个无栈协程,只有顶层例程可以被挂起。 该顶级例程调用的任何例程本身都不会暂停。 这禁止在内部的例程中提供暂停/恢复操作 通用库。
一流的延续
一流的延续可以通过 由函数返回并存储在数据结构中的参数 稍后使用。 在某些实现中(例如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的模拟限制。 在语言支持下,协程可见的自动存储变量将放置在协程的内部存储中。 请注意我对“协程可见”的强调,如果协程调用一个在内部使用自动存储变量的函数,则这些变量将被放置在运行时堆栈中。 更具体地说,无堆栈协程只需保留恢复后可以使用的变量/临时变量。
为了清楚起见,您还可以在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
只要定义不出现在任何await
之内。
- 我可以从无栈协程?
没有。
- 如果没有为无堆栈协程保存堆栈上下文,当协程是自动存储变量去哪里跑步?
上面回答的是,无堆栈协程并不关心被调用函数中使用的自动存储变量,它们只会放在正常的运行时堆栈中。
如果您有任何疑问,只需检查CO2的源代码,它可以帮助您了解幕后的技巧;)
您需要的是用户态线程/光纤-通常,您希望将代码(在光纤中运行)挂起在深度嵌套的调用堆栈中(例如,解析来自TCP连接的消息)。 在这种情况下,您不能使用无堆栈上下文切换(应用程序堆栈在无堆栈协程之间共享->被调用子例程的堆栈帧将被覆盖)。
您可以使用boost.fiber之类的东西,该东西基于boost.context实现用户端线程/纤维。