вторник, 27 июля 2021 г.

[prog.c++] Первые впечатления от C++ных короутин и зачем мне все это нужно?

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

В C++20 нет короутин

Понимаю, что на этот счет есть разные мнения, но лично мне после знакомства с тем, что включили в C++20, проще считать, что в C++20 короутин нет.

Есть механизм поддержки короутин со стороны языка.

А вот самих короутин нет.

ИМХО, это большая разница.

Если бы в стандартной библиотеке C++20 были готовые к использованию короутины, то у обычного разработчика занимало бы только то как посредством имеющихся короутин решить свою задачу.

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

Изучение механизма поддержки короутин в C++20 -- это та еще головоломка

Мягко говоря, это просто какой-то трындец.

Я вкуриваю эти самые короутины уже пять дней. И все еще никак.

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

При этом сам механизм поддержки короутин выглядит как очень крутой и гибкий. На уровне гениальности. Без всякой иронии. Просто поразительно как все придумано и продумано.

Проблема в моем случае в том, что моих мозгов на осознание всей гениальности замысла не хватает. Что расстраивает.

По большому счету детали механизма поддержки короутин в C++20 мало кому будут нужны

Расчет комитета по стандартизации C++ был в том, чтобы добавить в язык сам механизм. Чтобы те или иные реализации подтянулись со временем.

Как я понимаю, в Asio-1.19 эта реализация уже есть.

Вероятно, со временем и другие реализации для других прикладных ниш подтянутся.

Так что обычному разработчику нужно будет изучать не внутреннюю кухню языка в плане поддержки короутин, а возможности конкретной реализации короутин в конкретной библиотеке. Что гораздо проще.

Смущает, правда, наличие "закона текущих абстракций", согласно которому понимание того, что же происходит "под капотом" все же желательно. Но насколько все это будет актуально покажет только время. Будем посмотреть.

Ломать же голову над реализацией собственных короутин придется лишь тем немногим несчастным, кому потребуется сделать что-то свое. Таких явно будет немного.

Зачем мне это все?

Во-первых, это все равно будущее программирования на C++. Не удивлюсь, если через всего пять лет использование короутин будет обыденной и повседневной рутиной. Так что нужно подготовиться к грядущим изменениям.

Во-вторых, если наши SObjectizer и RESTinio продолжат свое развитие, то нужно понимать, где и как короутины могут пригодится. И уже сейчас кажется, что пригодиться могут.

Короутины в SObjectizer

Пока не видно, чтобы короутины нужны были где-то в потрохах SObjectizer-а.

Но вот где короутины явно могут помочь, так это в упрощении синхронного взаимодействия между агентами.

Сейчас если агент A хочет синхронно обратиться к агенту B (т.е. в обработчике событий E отослать сообщение агенту B и тут же, прямо внутри E дождаться ответа), то программист должен гарантировать что A и B работают на разных контекстах.

Тогда как в случае с короутинами можно сделать так, чтобы обработчик событий E сам был короутиной. Вызов операции ожидания ответа от B реализовывался бы через co_await. Например, что-то вроде:

so_5::awaitable_t A::evt_E(mhood_t<some_msg>) {
  // Создаем message chain для ответного сообщения.
  so_5::mchain_t reply_chain = so_5::create_mchain(so_environment());
  // Отсылаем сообщение агенту B и передаем в нем reply_chain, чтобы
  // агент B знал, куда отсылать ответ.
  so_5::send<some_request>(B_mbox, ..., reply_chain, ...);
  ...
  // Здесь мы хотим получить ответ.
  co_await so_5::receive(from(reply_chain).handle_n(1), ...);
}

Фокус здесь в том, что диспетчер, на котором запускается A::evt_E сможет понять, что здесь у нас короутина. И когда она приостановится на вызове co_await, то можно перейти к обслуживанию следующей заявки.

А это позволяет запросто привязать агентов A и B к общему рабочему контексту. При этом они смогут "синхронно" общаться друг с другом.

Что очень круто, т.к. раньше такое в принципе было невозможно.

Короутины в RESTinio

А вот в RESTinio для применения короутин открывается гораздо больший простор. ИМХО.

Короутины в реализации самого RESTinio

Прежде всего короутины можно использовать непосредственно в коде по работе с сетью в RESTinio. То, что сейчас делается на callback-ах, можно попробовать переделать на Asio-шных короутинах. И, есть подозрение, код внутри RESTinio получится более компактным и читабельным.

Короутины в реализации request_handler-ов

Очевидное применение короутин для упрощения жизни пользователям RESTinio -- это представление request_handler-ов в виде короутин.

Сейчас, если пользователь хочет сделать именно асинхронную обработку входящих запросов, то он должен в request_handler-е делегировать куда-то саму обработку и вернуть управление назад RESTinio.

В случае же, если request_handler будет представлен в виде короутины, то внутри request_handler-а можно инициировать асинхронные операции посредством co_await. Что, потенциально, сделает написание асинхронных обработчиков входящих запросов гораздо более простым занятием.

Поддержка исходящих соединений в RESTinio?

На данный момент RESTinio позволяет работать только с входящими HTTP-запросами, а исходящие запросы не поддерживаются в принципе. Т.е. RESTinio не может сейчас использоваться в качестве HTTP-клиента.

Причин тому множество (главное, что за поддержку этой фичи никто не захотел платить), но среди них есть и такой момент, что лично мне было непонятно, как должен был бы выглядеть интерфейс RESTinio для работы с исходящими соединениями.

А вот применение короутин, потенциально, может этот момент прояснить. Например, что-то вроде:

auto reply = co_await client.http_post(restinio::async, "https://google.com", ...);

Причем, если RESTinio будет поддерживать request_handler-ы в виде короутин, то подобный вызов можно будет делать прямо внутри request_handler-а. Т.е. приняли входящий запрос, внутри которого нужно сделать пару исходящих запросов к каким-то внешним сервисам -- нет проблем, сделали. Ничего не блокируя. Ну лепота же ж.

Я не хочу сказать, что наличие короутин делает добавление поддержки исходящих HTTP-запросов в RESTinio простым и понятным делом. Но внушает надежду, что такая поддержка может быть сделана красиво и удобно для пользователя.

Вместо заключения

В ближайший день-два я попробую написать пост, в котором расскажу, что именно затрудняет для меня разбирательство с механизмом короутин в C++20. Это нужно лично мне, т.к. надеюсь, что в процессе написания текста информация в мозгах структурируется чуть лучше. Но может кому-то это так же будет интересно.


Возможно, кто-то использует RESTinio в своих проектах и планирует со временем переходить на C++20. Если так, то вы можете быть заинтересованы в том, чтобы в RESTinio появилась поддержка короутин. Ускорить этот процесс можно за счет финансирования наших работ по RESTinio. Например, в виде договора между вашей компанией и СтифСтримом.

Тоже самое касается и SObjectizer-а.

Комментариев нет: