Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 05.12.25 21:32
Оценка:
В последнее время рабочие задачи стали сильно мигрировать, условно, с CPU-bound на IO-bound. Приходится миксовать логику и различные сетевые запросы и их ожидания. И чем навороченнее микс, тем больше приходит понимание, что синхронный подход тут начинает буксовать, код становится трудно поддерживать и менять бизнес логику всего этого. Тут вот подумалось, может быть спросить коллег, кто как подходит к IO-bound задачам, где CPU, будем считать для простоты, почти не используется и в основном приходится ждать завершение операций.

Что в идеале хочется получить:

data = await read(...);
await write(data);


Понятно, что скорее всего должен быть некий механизм, который следит за завершенными операциями и продолжает выполнение с точки ожидания.
Как такое можно организовать проще всего? Обязательно нужно кроссплатформенное решение, без погружения в дебри с ассемблером, регистрами и т.л.

Первое что приходит на ум С++20 c безстековыми корутинами. Но это С++20, а я пока на С++17 плотно сижу.
Можно ли еще как-то в С++17 организовать такой код, можно с колбеками/скрытими колбеками, может быть разбивать код в ручном режиме, но что бы он был более-менее понятен и можно было протаскивать объемный контекст между этой машиной состояний?

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

Если читаемый подход возможен только в С++20, то придется конечно дрейфовать туда, ничего не поделаешь.
Re: Асинхронщина
От: Великий Мессия google
Дата: 05.12.25 21:44
Оценка:
то есть все это время сидел на блокирующих операциях ?


ну бери asio
там все из коробки
есть и стековые корутины
и обычные
и колбеки
как хочешь, так и используй
Re: Асинхронщина
От: landerhigh Пират  
Дата: 05.12.25 22:31
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Первое что приходит на ум С++20 c безстековыми корутинами. Но это С++20, а я пока на С++17 плотно сижу.

V>Можно ли еще как-то в С++17 организовать такой код, можно с колбеками/скрытими колбеками, может быть разбивать код в ручном режиме, но что бы он был более-менее понятен и можно было протаскивать объемный контекст между этой машиной состояний?

Можно и нужно. Смотри сюда.

Тут колбеки пролучаются не совсем скрытыми, а скорее отделенными от логики

Можно взять стековые корутины из буста, но в целом для просто асинхронщины это будет стрельбой из пушки по воробъям

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


Библиотека корутин даст тебе только возможность yield/await. Ее еще предстоит подружить с задачей.
К примеру, асинхронное чтение/запись реализуются посредством вызова корутины из колбека, но сохранение контекста придется делать самому (имхо во многих случаях это не такая уж и проблема)
Re: Асинхронщина
От: Nuzhny Россия https://github.com/Nuzhny007
Дата: 06.12.25 09:22
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Если читаемый подход возможен только в С++20, то придется конечно дрейфовать туда, ничего не поделаешь.


Если посмотреть на userver, который делает всё то, что тебе надо, то видно, что он C++17 требует. То есть его достаточно.
Re[2]: Асинхронщина
От: Великий Мессия google
Дата: 06.12.25 09:29
Оценка:
Здравствуйте, Nuzhny, Вы писали:

N>Здравствуйте, Videoman, Вы писали:


V>>Если читаемый подход возможен только в С++20, то придется конечно дрейфовать туда, ничего не поделаешь.


N>Если посмотреть на userver, который делает всё то, что тебе надо, то видно, что он C++17 требует. То есть его достаточно.


asio тоже умеет стекфул корутины как в userver
то есть на С++17 тоже можно запустить


а так же на колбеках/лямбдах
как он того хочет
Re: Асинхронщина
От: kov_serg Россия  
Дата: 06.12.25 09:31
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

V>Как такое можно организовать проще всего? Обязательно нужно кроссплатформенное решение, без погружения в дебри с ассемблером, регистрами и т.л.

Обычный switch

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

int fn_setup(context *ctx,int event_code);
int fn_loop(context *ctx) {
  switch(ctx->line) { default:
  case 0: /* code 1 */ ctx->line=1; return 1;
  case 1: /* code 2 формируем запрос */ ctx->line=2; return 2; // предываемся и просми обработать запрос rc=2 (hint)
  case 2: /* code 3 анализируем ответ и был ли он вообще обработан */ ctx->line=3; return 1;
  case 3: /* code 4 */ ctx->line=4; return 1;
  /*...*/
  }
  return 0;
}

Функция которая имеет состояние и выполняется постепенно. "Квант исполнения" передаётся через метод loop. Управляющие воздействия через метод setup.
Если метод loop возвращает 0 значит функция отработала, не 0 значит еще требуются итерации. setup также 0-ок, не 0 значит что-то иное.
Для управления ресерсами можно использовать setup
setup(ctx,event_Init) -- подготаливаем функцию к работе и устанавливаем ctx->line=0
while( loop(ctx) ) {} -- проводим выполнение
setup(ctx,event_Done) -- освобождаем ресурсы

Обработку сетевых запросов, дочерних асинхронных функций, ожиданий и других прерываний обрабатывает планировщик (scheduler). В задачи которого так же входит убить все дочерние функции если их родительская померла. Это что бы структурированность была.

Дальше это всё ныкается в макросы с использованием __COUNTER__ или __LINE__ если __COUNTER__ не поддерживается.
  типа такого loop-fn.h
/* loop-fn.h - sequential execution function */
#ifndef __LOOP_FN_H__
#define __LOOP_FN_H__

typedef int loop_t;

#define LOOP_RESET(loop) { loop=0; }
#if defined(__COUNTER__) && __COUNTER__!=__COUNTER__
#define LOOP_BEGIN(loop) { enum { __loop_base=__COUNTER__ }; \
    loop_t *__loop=&(loop); __loop_switch: int __loop_rv=1; \
    switch(*__loop) { default: *__loop=0; case 0: {
#define LOOP_POINT { enum { __loop_case=__COUNTER__-__loop_base }; \
    *__loop=__loop_case; goto __loop_leave; case __loop_case:{} }
#else
#define LOOP_BEGIN(loop) {loop_t*__loop=&(loop);__loop_switch:int __loop_rv=1;\
    switch(*__loop){ default: case 0: *__loop=__LINE__; case __LINE__:{
#define LOOP_POINT { *__loop=__LINE__; goto __loop_leave; case __LINE__:{} }
#endif
#define LOOP_END { __loop_end: *__loop=-1; case -1: return 0; \
    { goto __loop_end; goto __loop_switch; /* make msvc happy */ } } \
    }} __loop_leave: return __loop_rv; }
#define LOOP_SET_RV(rv) { __loop_rv=(rv); } /* rv must be non zero */
#define LOOP_INT(n) { __loop_rv=(n); LOOP_POINT } /* interrupt n */
/* for manual labeling: enum { L01=1,L02,L03,L04 }; ... LOOP_POINT_(L02) */
#define LOOP_POINT_(name) { *__loop=name; goto __loop_leave; case name:{} }
#define LOOP_INT_(n,name) { __loop_rv=(n); LOOP_POINT_(name) }

#endif /* __LOOP_FN_H__ */
Из минусов надо помнить вы во switch и есть ограничения на вложенный switch, все временные переменные и структуры которые должны жить между преключениями надо объявлять явнов контексте функции. (Зато можно сохранять состояние на диск или передавать посети и возобновлять исполнение на другой машине да и работает на любой версии обычного C).

V>Если читаемый подход возможен только в С++20, то придется конечно дрейфовать туда, ничего не поделаешь.
Re[2]: Асинхронщина
От: Великий Мессия google
Дата: 06.12.25 09:52
Оценка:
asio в макросах такую же хрень умел делать
не знаю осталось ли в нем это еще
или уже почикали
Re[3]: Асинхронщина
От: kov_serg Россия  
Дата: 06.12.25 10:57
Оценка:
Здравствуйте, Великий Мессия, Вы писали:

ВМ>asio в макросах такую же хрень умел делать

ВМ>не знаю осталось ли в нем это еще
ВМ>или уже почикали

Хочется понять основные идеи и подходы

Re[4]: Асинхронщина
От: Великий Мессия google
Дата: 06.12.25 11:34
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>

Хочется понять основные идеи и подходы


помнится он уже как то пытался понять "идеи и подходы" в работе webrtc
в итоге взял и подключил готовую либу, по моему гугловскую

так что лучше всего взять готовый boost asio
сбилдить и разобраться в примерах которые там есть
а потом ее же и заюзать
Re[2]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 06.12.25 11:35
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>
_>int fn_setup(context *ctx,int event_code);
_>int fn_loop(context *ctx) {
_>  switch(ctx->line) { default:
_>  case 0: /* code 1 */ ctx->line=1; return 1;
_>  case 1: /* code 2 формируем запрос */ ctx->line=2; return 2; // предываемся и просми обработать запрос rc=2 (hint)
_>  case 2: /* code 3 анализируем ответ и был ли он вообще обработан */ ctx->line=3; return 1;
_>  case 3: /* code 4 */ ctx->line=4; return 1;
_>  /*...*/
_>  }
_>  return 0;
_>}
_>


Спасибо! Примерно то, что меня интересует но такой подход слишком низкоуровневый получается. Если рассматривать такой автомат как корутину, то придется хранить в состоянии всё, в том числе все локальные переменные и руками следить за их временем жизни, если они нужны только нескольким промежуточным состояниям. Больше С++ тут нельзя задействовать как-то?
Re[2]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 06.12.25 11:45
Оценка:
Здравствуйте, Nuzhny, Вы писали:

N>Здравствуйте, Videoman, Вы писали:


N>Если посмотреть на userver, который делает всё то, что тебе надо, то видно, что он C++17 требует. То есть его достаточно.


Мне показалось, что там во всю стекфул корутины используются. Подход понятен, но мне хотелось бы получить что-то в стиле node.js. Один основной поток, в котором логика строго однопоточная, без необходимости синхронизации. Мне показалось, что со стекфул корутинами, если мы используем сразу кучу ядер, код не отличается от многопоточного, т.к. мы не знаем на каком ядре та или иная ветка выполняется. С корутинами мы просто не ждем IO, но многопоточность никуда не девается, как я понял.

Уточню. У меня задача не запускать паралльно несколько независимых веток исполнения, а делать все по очереди (псевдопараллельно(, как будто в одном потоке, без синхронизации. Если на низком уровне рассматривать, похоже на большой автомат, но который каждый цикл выполняет одну и ту же логику, но она огромная размазана между кучи ожиданий IO, иногда вложенных. Похоже на то, как работает node.js. Ощущение, что безстековые корутины тут лучше подходят или я не прав?
Re[3]: Асинхронщина
От: kov_serg Россия  
Дата: 06.12.25 11:47
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Здравствуйте, kov_serg, Вы писали:


_>>
_>>int fn_setup(context *ctx,int event_code);
_>>int fn_loop(context *ctx) {
_>>  switch(ctx->line) { default:
_>>  case 0: /* code 1 */ ctx->line=1; return 1;
_>>  case 1: /* code 2 формируем запрос */ ctx->line=2; return 2; // предываемся и просми обработать запрос rc=2 (hint)
_>>  case 2: /* code 3 анализируем ответ и был ли он вообще обработан */ ctx->line=3; return 1;
_>>  case 3: /* code 4 */ ctx->line=4; return 1;
_>>  /*...*/
_>>  }
_>>  return 0;
_>>}
_>>


V>Спасибо! Примерно то, что меня интересует но такой подход слишком низкоуровневый получается. Если рассматривать такой автомат как корутину, то придется хранить в состоянии всё, в том числе все локальные переменные и руками следить за их временем жизни, если они нужны только нескольким промежуточным состояниям. Больше С++ тут нельзя задействовать как-то?


Можно использовать структуру/класс для хранения состояния. Но тут кстати RAII немного мешает поэтому лучше использовать аналог defer. То есть за ресурсы отвечает не исполнитель, а тот кто выдаёт задаиние исполнителю. В таком случае ресурсы можно контролировать (например ограничивать сверху).
struct Fn1 {
  int line;
  int i,j,k;

  int setup(int sev);
  int loop();

  static Fn1* my(void* ctx) { return (Fn1*)ctx; }
  static int _setup(void *ctx,int sev) { return my(ctx)->setup(sev); }
  static int _loop(void *ctx) { return my(ctx)->loop(); }
};

int Fn1::loop() {
   LOOP_BEGIN(line)
   for(i=1;i<10;i++) {
     // ...
     LOOP_POINT
     // ...
   }
   LOOP_END()
}

...
   Fn1 fn1[1];
   Fn1::_setup(fn1,sev_Init);
   while( Fn1::_loop(fn1) ) {}
   Fn1::_setup(fn1,sev_Done);

Или если больше нравиться можно использовать виртуальные методы, определив общий интерфейс.
struct AFn {
  virtual int setup(int)=0;
  virtual int loop()=0;
};

Но с функциями гибкость на порядок выше, особенно при использовании внешних динамических библиотек.
Re[5]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 06.12.25 11:50
Оценка:
Здравствуйте, Великий Мессия, Вы писали:

ВМ>так что лучше всего взять готовый boost asio

ВМ>сбилдить и разобраться в примерах которые там есть
ВМ>а потом ее же и заюзать

Что-то типа node.js можно на нем построить? Куча логики, с кучей вложенных друг в друга IO операций , которая по сути циклична. Покажи хотя бы на псевдокоде небольшом, как будет выглядеть.
Re[4]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 06.12.25 11:54
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Можно использовать структуру/класс для хранения состояния. Но тут кстати RAII немного мешает поэтому лучше использовать аналог defer. То есть за ресурсы отвечает не исполнитель, а тот кто выдаёт задаиние исполнителю. В таком случае ресурсы можно контролировать (например ограничивать сверху).

_>
_>struct Fn1 {
_>  int line;
_>  int i,j,k;

_>  int setup(int sev);
_>  int loop();

_>  static Fn1* my(void* ctx) { return (Fn1*)ctx; }
_>  static int _setup(void *ctx,int sev) { return my(ctx)->setup(sev); }
_>  static int _loop(void *ctx) { return my(ctx)->loop(); }
_>};

_>int Fn1::loop() {
_>   LOOP_BEGIN(line)
_>   for(i=1;i<10;i++) {
_>     // ...
_>     LOOP_POINT
_>     // ...
_>   }
_>   LOOP_END()
_>}

_>...
_>   Fn1 fn1[1];
_>   Fn1::_setup(fn1,sev_Init);
_>   while( Fn1::_loop(fn1) ) {}
_>   Fn1::_setup(fn1,sev_Done);
_>

_>Или если больше нравиться можно использовать виртуальные методы, определив общий интерфейс.
_>
_>struct AFn {
_>  virtual int setup(int)=0;
_>  virtual int loop()=0;
_>};
_>

_>Но с функциями гибкость на порядок выше, особенно при использовании внешних динамических библиотек.

Понял, спасибо. Но у тебя скорее С-шный подход. Всё состояние руками управляется. Такое понятно как сделать, но есть надежда, что можно автоматизировать как-то.
Re[5]: Асинхронщина
От: kov_serg Россия  
Дата: 06.12.25 12:04
Оценка:
Здравствуйте, Videoman, Вы писали:


V>Понял, спасибо. Но у тебя скорее С-шный подход. Всё состояние руками управляется. Такое понятно как сделать, но есть надежда, что можно автоматизировать как-то.

Да у меня C ная реализация. Потому как нажна портабельность была. Но её можно генерировать скриптами из dsl например с помощью lua, если хочется эстетического удовольствия.

ps: Более того при герерации кода из dsl вы неограничены императивным подходом, можно использовать декларативные конструкции и синтезировать код на основе поиска оптимального решения
Отредактировано 06.12.2025 12:06 kov_serg . Предыдущая версия .
Re: Асинхронщина
От: Великий Мессия google
Дата: 06.12.25 12:13
Оценка:
корутины которые офф появились в С++20
доступны с С++17
как coroutine_ts
во всяком случае asio пример есть для c++17 и компилируются с опцией компиля std++17
Re[6]: Асинхронщина
От: Великий Мессия google
Дата: 06.12.25 12:15
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, Videoman, Вы писали:



V>>Понял, спасибо. Но у тебя скорее С-шный подход. Всё состояние руками управляется. Такое понятно как сделать, но есть надежда, что можно автоматизировать как-то.

_>Да у меня C ная реализация. Потому как нажна портабельность была. Но её можно генерировать скриптами из dsl например с помощью lua, если хочется эстетического удовольствия.

_>ps: Более того при герерации кода из dsl вы неограничены императивным подходом, можно использовать декларативные конструкции и синтезировать код на основе поиска оптимального решения


хватит маяться дурю
это все уже сделано в asio
макрос BOOST_ASIO_CORO_*
Re[6]: Асинхронщина
От: Великий Мессия google
Дата: 06.12.25 12:17
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Здравствуйте, Великий Мессия, Вы писали:


ВМ>>так что лучше всего взять готовый boost asio

ВМ>>сбилдить и разобраться в примерах которые там есть
ВМ>>а потом ее же и заюзать

V>Что-то типа node.js можно на нем построить? Куча логики, с кучей вложенных друг в друга IO операций , которая по сути циклична. Покажи хотя бы на псевдокоде небольшом, как будет выглядеть.


https://www.boost.org/doc/libs/latest/doc/html/boost_asio/examples.html
выбирай любой пример
например для С++17
https://www.boost.org/doc/libs/latest/doc/html/boost_asio/example/cpp17/coroutines_ts/echo_server.cpp
Re[7]: Асинхронщина
От: kov_serg Россия  
Дата: 06.12.25 12:44
Оценка:
Здравствуйте, Великий Мессия, Вы писали:

ВМ>хватит маяться дурю это все уже сделано в asio макрос BOOST_ASIO_CORO_*

Там решаются совсем другие задачи Вообще есть kotlin пишите на нём.
Re: Асинхронщина
От: __kot2  
Дата: 06.12.25 12:45
Оценка:
Ну да, все правильно, обычные корутины нужны. А в чём проблема «перехода» на новый стандарт? У нас так вообще в конторе я так понял это чуть ли не автоматически делается
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.