Ответ 1
Документация верна. При реализации полудуплексного протокола, например HTTP Server 3, strand
не требуется. Цепочки вызовов могут быть проиллюстрированы следующим образом:
void connection::start()
{
socket.async_receive_from(..., &handle_read); ----.
} |
.------------------------------------------------'
| .-----------------------------------------.
V V |
void connection::handle_read(...) |
{ |
if (result) |
boost::asio::async_write(..., &handle_write); ---|--.
else if (!result) | |
boost::asio::async_write(..., &handle_write); --|--|
else | |
socket_.async_read_some(..., &handle_read); ----' |
} |
.---------------------------------------------------'
|
V
void handle_write(...)
Как показано на иллюстрации, только одно асинхронное событие запускается на каждый путь. Без возможности одновременного выполнения обработчиков или операций на socket_
он, как говорят, работает в неявной цепочке.
Безопасность резьбы
Пока он не является проблемой в этом примере, я хотел бы выделить одну важную деталь строк и сконфигурированных операций, таких как boost::asio::async_write
. Прежде чем объяснять детали, сначала включите модель безопасности потока с помощью Boost.Asio. Для большинства объектов Boost.Asio безопасно иметь несколько асинхронных операций, ожидающих обработки объекта; просто указано, что одновременные вызовы объекта небезопасны. На приведенных ниже диаграммах каждый столбец представляет поток, и каждая строка представляет, что поток делает в момент времени.
Безопасно, чтобы один поток выполнял последовательные вызовы, а другие потоки не выполняли:
thread_1 | thread_2 --------------------------------------+--------------------------------------- socket.async_receive(...); | ... socket.async_write_some(...); | ...
Безопасно для нескольких потоков выполнять вызовы, но не одновременно:
thread_1 | thread_2 --------------------------------------+--------------------------------------- socket.async_receive(...); | ... ... | socket.async_write_some(...);
Однако для нескольких потоков небезопасно совершать вызовы одновременно 1:
thread_1 | thread_2 --------------------------------------+--------------------------------------- socket.async_receive(...); | socket.async_write_some(...); ... | ...
Пряди
Чтобы предотвратить одновременные вызовы, обработчики часто вызывается изнутри строк. Это делается либо:
- Обертка обработчика с
strand.wrap
. Это вернет новый обработчик, который отправит через цепочку. - Проводка или отправка непосредственно через прядь.
Составленные операции уникальны в том, что промежуточные вызовы потока вызывают в цепочке обработчика, если он присутствует, а не в цепочке, в которой инициирована сгенерированная операция. По сравнению с другими операциями, это представляет собой инверсию, где указана прядь. Ниже приведен пример кода, ориентированного на использование строк, который будет демонстрировать сокет, который считывается с помощью несвязанной операции, и одновременно записывается с помощью сложенной операции.
void start()
{
// Start read and write chains. If multiple threads have called run on
// the service, then they may be running concurrently. To protect the
// socket, use the strand.
strand_.post(&read);
strand_.post(&write);
}
// read always needs to be posted through the strand because it invokes a
// non-composed operation on the socket.
void read()
{
// async_receive is initiated from within the strand. The handler does
// not affect the strand in which async_receive is executed.
socket_.async_receive(read_buffer_, &handle_read);
}
// This is not running within a strand, as read did not wrap it.
void handle_read()
{
// Need to post read into the strand, otherwise the async_receive would
// not be safe.
strand_.post(&read);
}
// The entry into the write loop needs to be posted through a strand.
// All intermediate handlers and the next iteration of the asynchronous write
// loop will be running in a strand due to the handler being wrapped.
void write()
{
// async_write will make one or more calls to socket_.async_write_some.
// All intermediate handlers (calls after the first), are executed
// within the handler context (strand_).
boost::asio::async_write(socket_, write_buffer_,
strand_.wrap(&handle_write));
}
// This will be invoked from within the strand, as it was a wrapped
// handler in write().
void handle_write()
{
// handler_write() is invoked within a strand, so write() does not
// have to dispatched through the strand.
write();
}
Важность типов обработчиков
Кроме того, в составных операциях Boost.Asio использует зависимый от аргумента поиск (ADL) для вызова промежуточных обработчиков через цепочку обработчика завершения. Таким образом, важно, чтобы тип обработчика завершения имел соответствующие asio_handler_invoke()
крючки. Если стирание типа происходит с типом, у которого нет соответствующих крючков asio_handler_invoke()
, например, в случае, когда a boost::function
создается из типа возврата strand.wrap
, тогда промежуточные обработчики будут выполняться за пределами строки и только обработчик завершения будет выполняться внутри нити. Подробнее см. .
В следующем коде все промежуточные обработчики и обработчик завершения будут выполняться внутри строки:
boost::asio::async_write(stream, buffer, strand.wrap(&handle_write));
В следующем коде в строке будет выполняться только обработчик завершения. Ни один из промежуточных обработчиков не будет выполняться внутри строки:
boost::function<void()> handler(strand.wrap(&handle_write));
boost::asio::async_write(stream, buffer, handler);
<суб > 1. история изменений документирует аномалию этого правила. Если поддерживается ОС, операции synchronous для чтения, записи, принятия и подключения являются потокобезопасными. Я включаю его здесь для полноты, но предлагаю использовать его с осторожностью.