【问题标题】:Safe cross platform coroutines安全的跨平台协程
【发布时间】:2011-12-19 22:11:15
【问题描述】:

我遇到的所有协程实现都使用汇编或检查jmp_buf 的内容。问题在于它本质上不是跨平台的。

我认为以下实现不会陷入未定义的行为或依赖于实现细节。但是我从来没有遇到过这样写的协程。

在线程中使用长跳是否存在一些固有缺陷?
这段代码中是否存在一些隐藏的问题?

#include <setjmp.h>
#include <thread>

class Coroutine
{
public:
   Coroutine( void ) :
      m_done( false ),
      m_thread( [&](){ this->start(); } )
   { }

   ~Coroutine( void )
   {
      std::lock_guard<std::mutex> lock( m_mutex );

      m_done = true;
      m_condition.notify_one();

      m_thread.join();
   }

   void start( void )
   {
      if( setjmp( m_resume ) == 0 )
      {
         std::unique_lock<std::mutex> lock( m_mutex );
         m_condition.wait( lock, [&](){ return m_done; } );
      }
      else
      {
         routine();
         longjmp( m_yield, 1 );
      }
   }

   void resume( void )
   {
      if( setjmp( m_yield ) == 0 )
      {
         longjmp( m_resume, 1 );
      }
   }

   void yield( void )
   {
      if( setjmp( m_resume ) == 0 )
      {
         longjmp( m_yield, 1 );
      }
   }

private:
   virtual void routine( void ) = 0;

   jmp_buf m_resume;
   jmp_buf m_yield;

   bool m_done;
   std::mutex m_mutex;
   std::condition_variable m_condition;
   std::thread m_thread;
};

【问题讨论】:

  • 协程在上个世纪很流行。当多核处理器变得普遍时,彻底过时了。除非这是出于学术兴趣,否则请务必利用线程并避免 setjmp() 的恐怖。
  • 为了并发,我对协程不感兴趣。它们有许多有用的特性,而穷人的并发不是其中之一。 Lua example 和 wikipedia reference
  • @Hans Passant -- 协程绝对不会消失,不管处理器有多少内核,因为上下文切换要快得多,你可以有两个订单协同例程比线程多,执行顺序有时很重要。
  • @Hans Passant -- 我认为我解释得不好。纤程(协程)不会引起内核上下文切换,它们在单个线程中运行,上下文切换就像一个跳远。我的第二点是因为它们在单个线程中运行,它们是非抢占式的。不仅没有锁定、竞态等,还保证了纤程的执行顺序。它们是偶数驱动模拟器中的基本原语,其中事件的排序是必不可少的。它们不能被抢占式线程替代。
  • @HansPassant:我认为并发和并行之间存在混淆。如果您看一下“新”语言,例如 Go 或 Haskell,您会注意到它们已针对并发性进行了定制,并提供了“轻量级”执行线程。它们不会从本质上增加应用程序的并行度(无论如何,您可以获得的最大并行度是受硬件限制的),但确实允许您定义数千个同时发展的轻量级任务。恕我直言,协程用于并发,可能适用于并行性,但不一定。

标签: c++ cross-platform c++11 coroutine


【解决方案1】:

更新 2013-05-13 现在有Boost Coroutine(基于Boost Context,尚未在所有目标平台上实现,但可能很快就会在所有主要平台上得到支持而不是以后)。


我不知道无堆栈协程是否适合您的预期用途,但我建议您在这里查看它们:

Boost Asio:The Proactor Design Pattern: Concurrency Without Threads

Asio 还具有基于单个 (IIRC) 简单预处理器宏的协同过程“仿真”模型,并结合了一些精心设计的模板工具,这些工具非常接近于编译器对 _stack-less 协同过程的支持。

示例 HTTP Server 4 是该技术的一个示例。

Boost Asio 的作者(Kohlhoff)在他的Blog here: A potted guide to stackless coroutines上解释了机制和示例

请务必查找该系列中的其他帖子!

【讨论】:

  • 哇!感谢您的阅读。将宏和行号与switch 语句结合使用确实很狡猾!
【解决方案2】:

我不相信你可以用跳远完全实现协程。 WinAPI 原生支持协程,它们被称为纤程。例如,请参阅CreateFiber()。我不认为其他操作系统有原生协程支持。如果您查看 SystemC 库,其中协同例程是核心部分,它们是在除 Windows 之外的每个受支持平台的汇编中实现的。 GBL 库还使用协程进行基于 Windows 光纤的事件驱动仿真。尝试实现协同例程和事件驱动设计时很容易难以调试错误,因此我建议使用已经过全面测试并具有更高级别抽象的现有库来处理此概念。

【讨论】:

  • 协程并不是操作系统的真正领域。相反,它们是特定于应用程序的 - 并且存在于用户空间中。
【解决方案3】:

没有通用的跨平台方式来实现协同程序。尽管某些实现可以使用 setjmp/longjmp 来伪造协同例程,但这种做法不符合标准。如果routine1 使用setjmp() 创建jmp_buf1,然后调用routine2() 使用setjmp() 创建jmp_buf2,任何longjmp() 到jmp_buf1 都会使jmp_buf2 失效(如果它还没有失效)。

我已经在各种 CPU 上完成了我所分享的协同例程实现;我总是至少使用一些汇编代码。它通常不需要太多(例如 8x51 上的任务切换需要 4 条指令),但使用汇编代码可以帮助确保编译器不会应用会破坏一切的创造性优化。

【讨论】:

  • 你不需要longjmp。事实上,这很糟糕并且会消耗内存。您可以仅使用宏将协程实现为隐式状态机。它既提供顺序查找的代码,又几乎没有内存开销。
  • @Martin:我已经完成了基于状态机的伪多任务处理,但我不将固定入口点状态机称为“协同程序”。我认为一个系统支持“真正的”协同程序,它必须可以从子程序调用中切换执行上下文,从而可以有阻塞的方法。
  • @supercat:您能否详细说明“任何 longjmp() 到 jmp_buf1 将使 jmp_buf2 无效”的确切机制?我认为您只是在暗示这样一个事实,即跳回routine1 会弹出routine2 的框架,因此之后“跳回”routine2 将是荒谬的;但我设法让自己在这个问题上感到困惑。 :) 如果我正确理解了上述情况,跳转到 jmp_buf1 将通过我描述的机制使 jmp_buf2 无效;但是跳转到 jmp_buf2 将不会使 jmp_buf1 无效。对吗?
  • @Quuxplusone:“机制”是标准规定如果代码在控制离开创建它的上下文后尝试使用 jmp_buf 可能发生的情况,它没有任何要求。是否真的发生了坏事取决于许多因素,其中一些可能是可以预测的,而另一些则不是。
【解决方案4】:

有一个C++ standard proposal for coroutine support - N3708,由 Oliver Kowalke(Boost.Coroutine 的作者)和 Goodspeed 编写。

我想这最终将是最终的清洁解决方案(如果它发生......) 因为我们没有来自 C++ 编译器的堆栈交换支持,协程目前需要低级别(通常是汇编级别,或 setjmp/longjmp)hack,这超出了 C++ 的抽象范围。然后实现很脆弱,需要编译器的帮助才能变得健壮。

例如,很难设置协程上下文的堆栈大小,如果堆栈溢出,您的程序将被无声地破坏。或者如果你幸运的话会崩溃。分段堆栈似乎可以对此有所帮助,但同样,这需要编译器级别的支持。

如果它成为标准,编译器编写者会小心的。但在那一天之前,Boost.Coroutine 对我来说将是 C++ 中唯一实用的解决方案。

在 C 中,libtask 由 Russ Cox(Go 团队的成员)编写。 libtask 工作得很好,但似乎不再维护了。

附:如果有人知道如何支持标准提案,请告诉我。我非常支持这个提议。

【讨论】:

  • 我在 C++ 和 C 中都使用了 protothreads。实际上,上面的协程只是重新打包的 protothreads 宏。
猜你喜欢
  • 2018-07-20
  • 2020-03-10
  • 1970-01-01
  • 2022-08-19
  • 2015-10-10
  • 1970-01-01
  • 2023-03-25
  • 2016-04-17
相关资源
最近更新 更多