Могу ли я порождать несколько сопрограмм на одной нити без их наложения?
Я пытаюсь призвать 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();
как я могу убедиться, что сопрограмма внутри конструктора завершена до того, как запустится функция in bar()?
1 ответ
strand
Только гарантируют, что их функции не будут выполняться одновременно в нескольких потоках. Это позволяет вам не использовать замки.
Они не заставляют отдельные функции выполняться последовательно. Если вы хотите последовательное выполнение, просто вызовите вторую функцию в конце вашей первой функции:
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()
каждый раз, когда вы хотите выполнить что-то.