无堆协程如何与堆栈协程不同?

时间:2015-03-11 01:57:33

标签: c++ concurrency coroutine boost-coroutine

背景

我问这个是因为我目前有一个包含许多(数百到数千)线程的应用程序。这些线程中的大多数在很长一段时间内处于空闲状态,等待将工作项放入队列中。当工作项可用时,然后通过调用一些任意复杂的现有代码来处理它。在某些操作系统配置中,应用程序会遇到管理最大用户进程数的内核参数,所以我想尝试减少工作线程数的方法。

我建议的解决方案:

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

实施细节:

在考虑如何做到这一点时,我无法理解无堆栈和堆栈协程之间的功能差异。我有使用Boost.Coroutine库的堆栈协程的一些经验。我发现从概念层面来理解它相对容易:对于每个协同程序,它维护着CPU上下文和堆栈的副本,​​当你切换到协程时,它会切换到保存的上下文(就像内核一样) -mode scheduler would)。

对我来说不太清楚的是无堆栈协程与此有何不同。在我的应用程序中,与上述工作项排队相关的开销量非常重要。我所见过的大多数实现,如the new CO2 library都表明无堆栈协程提供了更低开销的上下文切换。

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

  • References like this one表明区别在于你可以在堆叠与无堆叠协程中产生/恢复。是这样的吗?是否有一个简单的例子,我可以在一个堆叠的协程中做但不能在无堆栈协议中做什么?

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

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

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

2 个答案:

答案 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,因此内存使用也可以是改善。

说到无堆栈协程,请记住它并不意味着根本没有运行时堆栈,它只意味着它使用与主机端相同的运行时堆栈,所以你也可以调用递归函数,只是所有的递归都会在主机的运行时堆栈上发生。相反,对于堆栈协程,当你调用递归函数时,递归将发生在协同程序自己的堆栈上。

回答问题:

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

没有。这是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之前。

  • 我可以从a调用哪些函数有任何限制 stackless coroutine?

没有

  • 如果无堆栈协程没有保存堆栈上下文, 协程的自动存储变量在哪里 运行?

上面回答说,无堆栈协程并不关心被调用函数中使用的自动存储变量,它们只是放在正常的运行时堆栈上。

如果您有任何疑问,只需查看二氧化碳的源代码,它可以帮助您了解引擎盖下的机制;)

答案 1 :(得分:2)

你想要的是用户域线程/光纤 - 通常你想在深层嵌套调用堆栈中暂停你的代码(在光纤中运行)(例如从TCP连接解析消息)。在这种情况下,您不能使用无堆栈上下文切换(应用程序堆栈在无堆栈协程之间共享 - &gt;被覆盖的被调用子例程的堆栈帧)。

你可以使用类似boost.fiber的东西来实现基于boost.context的用户陆地线程/光纤。