Могу ли я порождать несколько сопрограмм на одной нити без их наложения? - PullRequest
0 голосов
/ 09 сентября 2018

Я пытаюсь вызвать boost::asio::spawn дважды к одному и тому же boost::asio::io_context::strand, передавая сопрограмму каждый раз, и я ожидал, что две сопрограммы будут выполняться одна за другой, но вместо этого они выполняются параллельно. Вот код, который иллюстрирует это:

boost::asio::io_context ioc;
boost::asio::io_context::strand strand{ioc};
boost::asio::spawn(strand, [&](boost::asio::yield_context yield)
                   {
                       cout << "1\n";
                       ioc.post(yield);
                       cout << "2\n";
                       ioc.post(yield);
                       cout << "3\n";
                   });

boost::asio::spawn(strand, [&](boost::asio::yield_context yield)
                   {
                       cout << "10\n";
                       ioc.post(yield);
                       cout << "20\n";
                       ioc.post(yield);
                       cout << "30\n";
                   });
ioc.run();

Это выводит:

1
10
2
20
3
30

Когда я ожидал:

1
2
3
10
20
30

В реальном коде первая сопрограмма устанавливает сокет (выполняет действия по разрешению / соединению / рукопожатию), а вторая выполняет отправку / получение. Мое намерение состояло в том, чтобы «добавить» вторую цепочку к цепочке и запустить ее только после завершения первой.

Как мне добиться этого эффекта?

Редактировать: больше контекста. Первая сопрограмма находится в конструкторе, вторая в функции-члене. Если я хочу разрешить пользователю писать

Foo foo;
foo.bar();

как я могу убедиться, что сопрограмма внутри конструктора завершена до того, как запустится функция в bar ()?

1 Ответ

0 голосов
/ 09 сентября 2018

strand s только гарантируют, что их функции не будут выполняться одновременно в нескольких потоках. Это позволяет вам не использовать замки.

Они не заставляют отдельные функции выполняться последовательно. Если вы хотите последовательное выполнение, просто вызовите вторую функцию в конце вашей первой функции:

boost::asio::io_context ioc;
boost::asio::io_context::strand strand{ioc};
auto main = [&](boost::asio::yield_context yield)
                   {
                       cout << "10\n";
                       ioc.post(yield);
                       cout << "20\n";
                       ioc.post(yield);
                       cout << "30\n";
                   };
boost::asio::spawn(strand, [&](boost::asio::yield_context yield)
                   {
                       cout << "1\n";
                       ioc.post(yield);
                       cout << "2\n";
                       ioc.post(yield);
                       cout << "3\n";
                       main();
                   });

Если вы не можете вызвать вторую функцию из первой, метод, который я использовал несколько раз, должен иметь очередь выполняемых функций, так как все в цепочке, не нужно беспокоиться о блокировке:

bool executing = false;
struct ExecuteLock
{
  ExecuteLock()
  {
    if ( !executing )
    {
      executing = true;
      locked = true;
    }
    else
    {
      locked = false;
    }
  }

  ~ExecuteLock()
  {
    if ( locked )
    {
      executing = false;
    }
  }

  bool locked;
};

typedef QueueFunction std::function<void(boost::asio::yield_context yield);

std::queue< QueueFunction > executeQueue;

void run( QueueFunction f )
{
  boost::asio::spawn( strand, [=](boost::asio::yield_context yield)
  {
    ExecuteLock lock;
    if (!lock.locked)
    {
      executeQueue.push( f );
      return;
    }
    f();
    while ( !executeQueue.empty() )
    {
      executeQueue.front()();
      executeQueue.pop();
    }
  } );
}

Затем вы можете просто звонить run() каждый раз, когда хотите что-то выполнить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...