Понимание использования пряди без блокировки

Справка: websocket_client_async_ssl.cppпряди

Вопрос 1> Вот как я понимаю:

Given a few async operations bound with the same strand, the strand will guarantee that all associated async operations will be executed as a strictly sequential invocation.

Означает ли это, что все вышеперечисленные асинхронные операции будут выполняться одним и тем же потоком? Или это просто говорит о том, что в любое время любой доступный поток будет выполнять только одну асинхронную операцию?

вопрос 2> Функция boost::asio::make_strand создает объект цепочки для исполнителя или контекста выполнения.

session(net::io_context& ioc, ssl::context& ctx)
    : resolver_(net::make_strand(ioc))
    , ws_(net::make_strand(ioc), ctx)
    

Здесь у resolver_ и ws_ есть своя цепочка, но у меня проблемы с пониманием того, как каждая прядь относится к каким асинхронным операциям.

Например, в следующем примере aysnc и обработчик, функции (например, aysnc или обработчик) привязаны к одной и той же цепочке и не будут выполняться одновременно.

run
  =>resolver_.async_resolve -->session::on_resolve 
    =>beast::get_lowest_layer(ws_).async_connect -->session::on_connect
      =>ws_.next_layer().async_handshake --> session::on_ssl_handshake
        =>ws_.async_handshake --> session::on_handshake

асинхронный ================================= обработчик

Вопрос 3> Как мы можем получить цепочку от исполнителя? Есть ли разница между этими двумя?

get_associated_executorget_executor

io_context::get_executor: Obtains the executor associated with the io_context.

get_associated_executor: Helper function to obtain an object's associated executor.

Вопрос 4> Правильно ли я использую следующий метод для привязки deadline_timer к io_context, чтобы предотвратить состояние гонки?

Все остальные части кода такие же, как в примере websocket_client_async_ssl.cpp.

session(net::io_context& ioc, ssl::context& ctx)
    : resolver_(net::make_strand(ioc))
    , ws_(net::make_strand(ioc), ctx),
    d_timer_(ws_.get_executor())
{     }

void on_heartbeat_write( beast::error_code ec, std::size_t bytes_transferred)
{
  d_timer_.expires_from_now(boost::posix_time::seconds(5));
  d_timer_.async_wait(beast::bind_front_handler( &session::on_heartbeat, shared_from_this()));
}

void on_heartbeat(const boost::system::error_code& ec)
{
    ws_.async_write( net::buffer(text_ping_), beast::bind_front_handler( &session::on_heartbeat_write, shared_from_this()));
}

void on_handshake(beast::error_code ec)
{
    d_timer_.expires_from_now(boost::posix_time::seconds(5));
    d_timer_.async_wait(beast::bind_front_handler( &session::on_heartbeat, shared_from_this()));
    ws_.async_write(net::buffer(text_), beast::bind_front_handler(&session::on_write, shared_from_this()));
}

Примечание: Я использовал d_timer_(ws_.get_executor()) для инициализации deadline_timer и надеялся, что они не будут писать или читать веб-сокет одновременно. Это правильный способ сделать это?

В интересах качества вопросов и ответов было бы лучше, если бы вы могли задавать меньше вопросов в посте. Ваша нумерация довольно щедрая, я думаю, что видел как минимум 7 вопросов. В данном случае они скорее связаны, так что я потакал, но, возможно, если бы вы задали всего один вопрос, вы могли бы получить ту же информацию, возможно, более последовательно. Я не уверен, что это сработает, но, возможно, стоит попробовать.

sehe 19.03.2022 05:32

Извини за это. В будущем я ограничу один вопрос на пост. Благодарю вас!

q0987 19.03.2022 15:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
41
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вопрос 1

Does this mean that all above async operations will be executed by a same thread? Or it just says that at any time, only one async operation will be executed by any available thread?

Последний.

вопрос 2

Here, resolver_ and ws_ have its own strand,

Позвольте мне вставить, что я думаю, что это излишне сбивает с толку в примере. Они могли (теоретически должны были) использовать одну и ту же цепочку, но я думаю, они не хотели мучиться с хранением пряди. Я бы, наверное, написал:

explicit session(net::io_context& ioc, ssl::context& ctx)
    : resolver_(net::make_strand(ioc))
    , ws_(resolver_.get_executor(), ctx) {}

Функции инициализации называются где вы решаете. Обработчики завершения dispatch-ed для исполнителя, принадлежащего объекту ввода-вывода, для которого вы вызываете операцию, пока не обработчик завершения привязан к другому исполнителю (например, с помощью bind_executor, см. get_associated_exectutor). В большинстве случаев в современном Asio вы будете привязывать обработчики нет вместо того, чтобы «привязывать объекты ввода-вывода» к соответствующим исполнителям. Это позволяет меньше печатать и гораздо труднее забыть.

Таким образом, все асинхронные инициации в цепочке кроме для одной из run() находятся в цепочке, потому что объекты ввода-вывода привязаны к исполнителям цепочек.

Ты должен помнить об отправке в цепочку, когда какой-либо пользователь за пределами вызывает ваши классы (например, часто stop). Поэтому было бы неплохо разработать конвенцию. Я бы лично сделал все "небезопасные" методы и элементы private:, поэтому у меня часто будут такие пары, как:

  public:
    void stop() {
        dispatch(strand_, [self=shared_from_this()] { self->do_stop(); });
    }

  private:
    void do_stop() {
        beast::get_lowest_layer(ws_).cancel();
    }

Side Note:

In this particular example, there is only one (main) thread running/polling the io service, so the whole point is moot. But as I explained recently (Does mulithreaded http processing with boost asio require strands?), the examples are here to show some common patterns that allow one to do "real life" work as well

Бонус: отслеживание обработчиков

Давайте воспользуемся BOOST_ASIO_ENABLE_HANDLER_TRACKING, чтобы получить некоторое представление.¹ Запуск пробного сеанса показывает что-то вроде

Если немного прищуриться, то можно увидеть, что все исполнители прядей одинаковы:

0*1|[email protected]_resolve
1*2|[email protected]
2*3|[email protected]_connect
3*4|[email protected]
4*5|[email protected]_send
5*6|[email protected]
6*7|[email protected]_receive
7*8|[email protected]
8*9|[email protected]_send
9*10|[email protected]
10*11|[email protected]_receive
11*12|[email protected]
12*13|[email protected]_wait
12*14|[email protected]_send
14*15|[email protected]
15*16|[email protected]_receive
16*17|[email protected]
17*18|[email protected]_send
13*19|[email protected]
18*20|[email protected]
20*21|[email protected]_receive
21*22|[email protected]
22*23|[email protected]_wait
22*24|[email protected]_send
24*25|[email protected]
25*26|[email protected]_receive
26*27|[email protected]
23*28|[email protected]

Вопрос 3

How can we retrieve the strand from executor?

Вы не[*]. Однако make_strand(s) возвращает эквивалентную цепочку, если s уже является цепочкой.

[*] По умолчанию объекты ввода-вывода Asio используют исполнителя с удаленным типом (asio::executor или asio::any_io_executor в зависимости от версии). Итак, технически вы мог спрашиваете его о его target_type() и после сравнения идентификатора типа с некоторыми ожидаемыми типами используете что-то вроде target<net::strand<net::io_context::executor_type>>() для доступа к оригиналу, но на самом деле это бесполезно. Вы не хотите проверять детали реализации. Просто уважайте обработчики (отправляя их соответствующим исполнителям, как это делает Asio).

Is there any difference between these two? get_associated_executor get_executor

get_executor получает собственного исполнителя из объекта ввода-вывода. Это функция-член.

asio::get_associated_executor получает связанных исполнителей из объектов-обработчиков. Вы заметите, что get_associated_executor(ws_) не компилируется (хотя некоторые объекты ввода-вывода могут иметь удовлетворять критериям, чтобы он работал).

Вопрос 4

Is it correct that I use the following method to bind deadline_timer to io_context

Вы заметите, что сделали то же самое, что я уже упоминал выше, чтобы привязать объект ввода-вывода таймера к тому же исполнителю цепочек. Итак, респект.

to prevent race condition?

Вы не предотвращаете условия гонки здесь. Вы предотвращаете гонки данных. Это потому, что в on_heartbeat вы доступ объект ws_, который является экземпляром класса, который НЕ является потокобезопасным. По сути, вы делитесь доступом к ресурсам, не поддерживающим потокобезопасность, и вам нужен доступ сериализовать, поэтому вы хотите быть на той же нити, на которой также находятся все остальные доступы.

Note: [...] and hoped that it will make sure they don't write or read the websocket at the same time. Is this the right way to do it?

Да, это хорошее начало, но этого недостаточно.

Во-первых, вы могу пишете или читаете одновременно, пока

  • операции записи не перекрываются
  • операции чтения не пересекаются
  • обращения к объекту ввода-вывода безопасно сериализуются.

В частности, ваш on_heartbeat может быть безопасно сериализован, поэтому у вас не будет гонки данных при вызове функции инициации async_write. Однако вам нужно больше проверок, чтобы узнать, выполняется ли уже (еще) операция записи. Один из способов добиться этого — создать очередь с исходящими сообщениями. Если у вас строгие требования к пульсу и высокая нагрузка, вам может понадобиться приоритетная очередь.


¹ Я упростил пример, заменив тип потока на родной Asio ssl::stream<tcp::socket>. Это означает, что мы не получаем все внутренние таймеры, которые имеют дело с истечениями tcp_stream. См. https://pastebin.ubuntu.com/p/sPRYh6Xbwz/

Другие вопросы по теме