c ++-无栈协程与有栈协程有何不同?

背景:

我之所以这么问,是因为我目前有一个具有许多(数百至数千)线程的应用程序。 这些线程中的大多数在大部分时间都处于空闲状态,等待将工作项放入队列中。 当工作项可用时,然后通过调用一些任意复杂的现有代码对其进行处理。 在某些操作系统配置上,应用程序会遇到控制最大用户进程数的内核参数,因此我想尝试减少工作线程数的方法。

我建议的解决方案:

似乎是基于协程的方法,用协程替换每个工作程序线程将有助于实现此目的。 然后,我可以有一个由实际(内核)工作线程池支持的工作队列。 将项目放置在特定协程的队列中进行处理时,会将条目放置在线程池的队列中。 然后它将恢复相应的协程,处理其排队的数据,然后再次将其暂停,以释放工作线程来执行其他工作。

实施细节:

在考虑如何执行此操作时,我很难理解无栈协程和有栈协程之间的功能差异。 我有一些使用Boost.Coroutine库使用堆栈协程的经验。 我发现从概念上理解相对容易:对于每个协程,它维护CPU上下文和堆栈的副本,并且当您切换到协程时,它将切换到该保存的上下文(就像内核模式调度程序那样 )。

我不太清楚的是无栈协程与此有何不同。 在我的应用程序中,与上述工作项排队相关的开销非常重要。 我见过的大多数实现(例如新的CO2库)都建议无堆栈协程提供开销更低的上下文切换。

因此,我想更清楚地了解无栈协程和有栈协程之间的功能差异。 具体来说,我想到了以下问题:

  • 像这样的引用表明,区别在于您可以在有堆栈的协程与无堆栈的协程中屈服/恢复。 是这样吗 是否有一个简单的示例,说明我可以在堆栈式协程中执行某些操作,但不能在无堆栈的协程中执行某些操作?

  • 使用自动存储变量(即“在堆栈上”的变量)是否有任何限制?

  • 我可以从无堆栈协程调用哪些函数有任何限制?

  • 如果没有为无堆栈协程保存堆栈上下文,那么当协程运行时,自动存储变量会移到哪里?

Jason R asked 2020-01-07T02:55:45Z
2个解决方案
49 votes

首先,谢谢您对二氧化碳的关注:)

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的源代码,它可以帮助您了解幕后的技巧;)

Jamboree answered 2020-01-07T02:57:50Z
2 votes

您需要的是用户态线程/光纤-通常,您希望将代码(在光纤中运行)挂起在深度嵌套的调用堆栈中(例如,解析来自TCP连接的消息)。 在这种情况下,您不能使用无堆栈上下文切换(应用程序堆栈在无堆栈协程之间共享->被调用子例程的堆栈帧将被覆盖)。

您可以使用boost.fiber之类的东西,该东西基于boost.context实现用户端线程/纤维。

olk answered 2020-01-07T02:58:15Z
translate from https://stackoverflow.com:/questions/28977302/how-do-stackless-coroutines-differ-from-stackful-coroutines