Безопасные кроссплатформенные сопрограммы - PullRequest
9 голосов
/ 02 ноября 2011

Все реализации сопрограмм, с которыми я сталкивался, используют сборку или проверяют содержимое 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;
};

Ответы [ 4 ]

9 голосов
/ 02 ноября 2011

ОБНОВЛЕНИЕ 2013-05-13 В эти дни существует Boost Coroutine (построено на Boost Context , которое реализовано не на всех целевых платформахпока, но, вероятно, будет поддерживаться на всех основных платформах раньше, чем позже).


Я не знаю, подходят ли сопрограммы без стеков к предполагаемому использованию, но я предлагаю вампосмотрите на них здесь:

Boost Asio: Шаблон проектирования Proactor: параллелизм без потоков

В Asio также есть модель эмуляции сопроцедур, основанная на единственном(IIRC) простой макрос препроцессора, в сочетании с некоторым количеством хитроумно разработанных шаблонных средств, которые очень похожи на поддержку компилятором _стак-без-процедур.

Пример HTTP-сервер 4 является примером этой техники.

Автор Boost Asio (Kohlhoff) объясняет механизм и пример в своем блоге здесь: руководство в горшке к сопрограмме без стековs

Обязательно поищите другие посты в этой серии!

6 голосов
/ 26 октября 2013

Существует стандартное предложение C ++ для поддержки сопрограмм - N3708 , написанное Оливером Ковальке (автором Boost.Coroutine ) и Гудспид .

Полагаю, в конечном итоге это будет окончательно чистое решение (если это произойдет ...) Поскольку у нас нет поддержки обмена стека от компилятора C ++, сопрограммам в настоящее время требуется взлом на низком уровне (обычно на уровне сборки или setjmp / longjmp), и это вне диапазона абстракции C ++. Тогда реализации являются хрупкими, и им нужна помощь компилятора, чтобы быть надежным.

Например, очень сложно установить размер стека контекста сопрограммы, и если вы переполните стек, ваша программа будет молча повреждена. Или сбой, если вам повезет. Сегментированный стек может помочь в этом, но опять же, для этого нужна поддержка на уровне компилятора.

Если он станет стандартным, об этом позаботятся авторы компилятора. Но до этого дня Boost.Coroutine был бы для меня единственным практическим решением на C ++.

В C есть libtask, написанный Russ Cox (который является членом команды Go). libtask работает довольно хорошо, но, похоже, больше не поддерживается.

P.S. Если кто-то знает, как поддержать стандартное предложение, пожалуйста, дайте мне знать. Я действительно поддерживаю это предложение.

4 голосов
/ 03 ноября 2011

Не существует обобщенного кроссплатформенного способа реализации сопрограмм.Хотя некоторые реализации могут выдумывать сопрограммы, используя setjmp / longjmp, такие практики не соответствуют стандартам.Если рутина1 использует setjmp () для создания jmp_buf1, а затем вызывает рутину2 (), которая использует setjmp () для создания jmp_buf2, любой longjmp () для jmp_buf1 сделает недействительным jmp_buf2 (если он еще не был аннулирован).

Я сделал свою долю совместных реализаций на различных процессорах;Я всегда использовал хоть какой-то ассемблерный код.Зачастую это не занимает много времени (например, четыре инструкции для переключения задач на 8x51), но использование кода на ассемблере может помочь гарантировать, что компилятор не будет применять творческие оптимизации, которые сломали бы все.

2 голосов
/ 02 ноября 2011

Я не верю, что вы можете полностью реализовать сопрограммы с прыжками в длинуСовместные подпрограммы изначально поддерживаются в WinAPI, они называются волокнами.См. Например, CreateFiber () .Я не думаю, что другие операционные системы имеют встроенную поддержку рутинной поддержки.Если вы посмотрите на библиотеку SystemC, для которой подпрограммы являются центральной частью, они реализованы в сборке для каждой поддерживаемой платформы, кроме Windows.Библиотека GBL также использует сопрограммы для моделирования событий на основе волокон Windows.Очень сложно отлаживать ошибки, пытаясь реализовать сопрограммы и дизайн, управляемый событиями, поэтому я предлагаю использовать существующие библиотеки, которые уже тщательно протестированы и имеют абстракции более высокого уровня, чтобы справиться с этой концепцией.

...