C++ 无堆栈协同路由与有堆栈协同路由有何不同?

C++ 无堆栈协同路由与有堆栈协同路由有何不同?,c++,concurrency,coroutine,boost-coroutine,C++,Concurrency,Coroutine,Boost Coroutine,背景: 我问这个问题是因为我目前有一个应用程序有许多(数百到数千)线程。这些线程中的大多数在很大一部分时间都处于空闲状态,等待将工作项放入队列。当一个工作项可用时,通过调用一些任意复杂的现有代码来处理它。在某些操作系统配置中,应用程序会遇到控制最大用户进程数的内核参数,因此我想尝试减少工作线程数的方法 我建议的解决方案: 这似乎是一种基于协程的方法,我用一个协程替换每个工作线程,这将有助于实现这一点。然后,我可以拥有一个由实际(内核)工作线程池支持的工作队列。当一个项目被放置在特定协同程序的队列

背景:

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

我建议的解决方案:

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

实施细节:

在思考如何做到这一点时,我很难理解无堆栈和有堆栈协同路由之间的功能差异。我有一些使用库的堆栈式协程的经验。我发现从概念层面上理解它相对容易:对于每个协程,它维护CPU上下文和堆栈的副本,当您切换到协程时,它切换到保存的上下文(就像内核模式调度器一样)

我不太清楚的是无堆栈协同程序与此的区别。在我的应用程序中,与上述工作项排队相关的开销量非常重要。我所看到的大多数实现,比如,都表明无堆栈协同路由提供了开销更低的上下文切换

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

  • 建议区别在于,在堆叠式和无堆叠式协同程序中,您可以在哪里让步/恢复。是这样吗?有没有一个简单的例子,说明我可以在一个有堆栈的协同程序中做些什么,但不能在一个无堆栈的协同程序中做

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

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

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


    • 首先,感谢您查看:)

      Boost.Coroutine很好地描述了堆栈式协同路由的优点:

      堆叠性

      与无堆栈协同路由不同的是,有堆栈协同路由 可以从嵌套的堆栈框架内暂停。执行于 与代码中之前暂停的位置完全相同。具有 对于无堆栈协同例程,只有顶级例程可以挂起。 该顶级例程调用的任何例程本身都不能挂起。 这禁止在中的例程中提供挂起/恢复操作 通用图书馆

      头等舱续航

      一级延拓可以作为 一种参数,由函数返回并存储在数据结构中,用于 以后再使用。在某些实现中(例如C#yield) 无法直接访问或直接操作continuation

      如果没有堆叠性和一级语义,一些有用的执行 无法支持控制流(例如协作流) 多任务或检查点)

      这对你意味着什么?例如,假设您有一个函数,该函数接受访问者:

      template<class Visitor>
      void f(Visitor& v);
      
      模板
      无效f(访客和v);
      
      如果要将其转换为迭代器,使用stackful协程,您可以:

      asymmetric_coroutine<T>::pull_type pull_from([](asymmetric_coroutine<T>::push_type& yield)
      {
          f(yield);
      });
      
      非对称协程::拉式从([])拉式(非对称协程::推式和屈服)
      {
      f(收益率);
      });
      
      但使用stackless协同程序,无法做到这一点:

      generator<T> pull_from()
      {
          // yield can only be used here, cannot pass to f
          f(???);
      }
      
      generator pull_from()
      {
      //收益率只能在此处使用,不能传递给f
      f(??);
      }
      
      一般来说,stackful协程比stackless协程更强大。 那么为什么我们想要stackless协同程序呢?简单回答:效率

      Stackful协程通常需要分配一定数量的内存来容纳其运行时堆栈(必须足够大),并且上下文切换比stackless切换更昂贵,例如Boost。协程需要40个周期,而CO2在我的机器上平均只需要7个周期,因为无堆栈协程需要恢复的唯一东西是程序计数器

      这就是说,有了语言支持,只要协程中没有递归,可能堆栈式协程还可以利用编译器为堆栈计算的最大大小,因此也可以提高内存使用率

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

      回答以下问题:

      • 自动存储变量的使用是否有任何限制 (即变量“在堆栈上”)
      不,这是二氧化碳的模拟极限。有了语言支持,协同程序可见的自动存储变量将被放置在协同程序的内部存储器上。注意我强调的“对c可见”
      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