Асинхронщина
От: 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
Оценка: +1
то есть все это время сидел на блокирующих операциях ?


ну бери 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
Оценка:
Ну да, все правильно, обычные корутины нужны. А в чём проблема «перехода» на новый стандарт? У нас так вообще в конторе я так понял это чуть ли не автоматически делается
Re[3]: Асинхронщина
От: landerhigh Пират  
Дата: 06.12.25 13:29
Оценка: +1
Здравствуйте, Videoman, Вы писали:

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


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


Именно что показалось.
Корутина — это не поток. Это просто возможность прервать выполнение функции и потом продолжить с того же места. Когда прервать и когда продолжить, решать программисту, т.е. тебе.

V>Уточню. У меня задача не запускать паралльно несколько независимых веток исполнения, а делать все по очереди (псевдопараллельно(, как будто в одном потоке, без синхронизации. Если на низком уровне рассматривать, похоже на большой автомат, но который каждый цикл выполняет одну и ту же логику, но она огромная размазана между кучи ожиданий IO, иногда вложенных. Похоже на то, как работает node.js. Ощущение, что безстековые корутины тут лучше подходят или я не прав?


Тут подходят безстековые корутины. И стековые тоже подходят.
Разница между ними в том, что в случае безстековых корутин сохранение контекста либо делается вручную, либо должно поддерживаться языком/средой (в случае 20х корутин это делает компилятор, как правило выделяя память на куче). Для стековых корутин должен выделяться отдельный стек, и запуск/выход из них связан с необходимостью сохранения и восстановления всех регистров, что относительно дорого. Из плюсов стековых корутин можно назвать то, что переключение контекста возможно из любой функции в стеке вызовов, при этом функции выше по стеку вообще могут не знать, что они выполняются в контексте корутины.
Re: Асинхронщина
От: AleksandrN Россия  
Дата: 06.12.25 18:47
Оценка:
Здравствуйте, Videoman, Вы писали:

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


Для ожидания события в сокете можно использовать poll/epoll/select/kqueue (сокет должен быть не блокируемым). Но кросс-платформенный из этого только select.
В кроссплатформенных библиотеках boost::asio, libevent и ещё нескольких именно эти механизмы и используются.

Можно коллбек прикрутить, можно в отдельном потоке ответ ждать, причём можно ждать ответ более, чем на одном сокете.
Re: Асинхронщина
От: Pitirimov США  
Дата: 07.12.25 22:37
Оценка:
Здравствуйте, Videoman, Вы писали:
V> ... кто как подходит к IO-bound задачам, где CPU, будем считать для простоты, почти не используется и в основном приходится ждать завершение операций.

Я бы просто запускал новый поток и ожидал сообщения от него о завершении обмена данными. Ядер у современных процессоров гораздо больше одного и в наши дни неразумно обрабатывать все задачи сопрограммами на одном ядре процессора вместо многопоточной, многоядерной обработки.
Re[2]: Асинхронщина
От: kov_serg Россия  
Дата: 08.12.25 07:28
Оценка:
Здравствуйте, Pitirimov, Вы писали:

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

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

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


Дык если потоков много очень большие накладные расходы по памяти и на переключение, да и кэш бодро вымывается.
Поэтому вместо миллиона тактов на переключения потока использоую сотни тактов на планировщик и вместа 4мб стека на поток мелкие стуктуры и coroutine-ы.
В результате выигрыш по производительности может быть очень ощутимым. Если бы он был не значительным миллионы мух не заморачивались бы.
Отредактировано 08.12.2025 7:29 kov_serg . Предыдущая версия .
Re[2]: Асинхронщина
От: so5team https://stiffstream.com
Дата: 08.12.25 07:33
Оценка:
Здравствуйте, Pitirimov, Вы писали:

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

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

P>Я бы просто запускал новый поток и ожидал сообщения от него о завершении обмена данными.


Довелось лет 5 назад понаблюдать за тем, как Linux вставал колом на такой модели thread per connection когда количество одновременно обслуживаемых соединений превышало 32K штук.

Не надо так делать.
Re[2]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 08.12.25 18:52
Оценка:
Здравствуйте, Pitirimov, Вы писали:

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


В моих новых задачах мне такое не подходит. Говорю же CPU-bound. Основному потоку нечего желать, только логику распределять, всё на ожидании висит. Вот только автомат по логике получается безумный. Поэтому хочется всё это выпрямить. Зачем тут стрелять из пушки.
Re[4]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 08.12.25 19:13
Оценка:
Здравствуйте, landerhigh, Вы писали:

L>Именно что показалось.

L>Корутина — это не поток. Это просто возможность прервать выполнение функции и потом продолжить с того же места. Когда прервать и когда продолжить, решать программисту, т.е. тебе.

А я где-то написал что корутина это — поток . Корутина это не поток, но выполняется она не в вакуме, а в контексте потока ОС.

L>Тут подходят безстековые корутины. И стековые тоже подходят.

L>Разница между ними в том, что в случае безстековых корутин сохранение контекста либо делается вручную, либо должно поддерживаться языком/средой (в случае 20х корутин это делает компилятор, как правило выделяя память на куче). Для стековых корутин должен выделяться отдельный стек, и запуск/выход из них связан с необходимостью сохранения и восстановления всех регистров, что относительно дорого. Из плюсов стековых корутин можно назвать то, что переключение контекста возможно из любой функции в стеке вызовов, при этом функции выше по стеку вообще могут не знать, что они выполняются в контексте корутины.

Я знаю чем стековые корутины отличаются от безстековых, только вот разница большая, на мой взгляд. Как я понимаю, в случае стековых корутин мне придется всё равно писать в многопоточном стиле, пофигу что физически поток один, нити то разные и стеки тоже. Потом придется как-то контекст руками размазывать по нитям, делать аналог join. Отличий от потоков почти никакой, с той лишь разницей, что мы оптимизируем переключение контекста оптимизирую по CPU, а мне это не нужно, хотелось бы логику выпрямить.
Меня интересуют безстековые корутины или их заменители, когда вся логика в основном потоке. Еще большой плюс, что можно использовать пул потоков, в которых выполнять асинхронные операции изолированно, а результат возвращать основному потоку. Видимо придется использовать 20-й стандарт.

А на 20-м стандарте есть какие-нибудь библиотеки, которые использую корутины оттуда? Была бы идеальна обертка libuv в С++20 корутины, кажется.
Re[5]: Асинхронщина
От: so5team https://stiffstream.com
Дата: 09.12.25 05:51
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

V>А на 20-м стандарте есть какие-нибудь библиотеки, которые использую корутины оттуда? Была бы идеальна обертка libuv в С++20 корутины, кажется.


На Reddit-е на днях кто-то запостил ссылку на свою такую обертку: https://old.reddit.com/r/cpp/comments/1pep8ie/introducing_asyncio_a_new_opensource_c23/
Но она требует C++23.
Re: Асинхронщина
От: B0FEE664  
Дата: 09.12.25 13:59
Оценка:
Здравствуйте, Videoman, Вы писали:

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

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

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

std::future, std::async
И каждый день — без права на ошибку...
Re[2]: Асинхронщина
От: sergii.p  
Дата: 09.12.25 15:24
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>std::future, std::async


это же создаст сонм потоков-бездельников
Re[5]: Асинхронщина
От: Великий Мессия google
Дата: 09.12.25 17:41
Оценка:
V>А на 20-м стандарте есть какие-нибудь библиотеки, которые использую корутины оттуда? Была бы идеальна обертка libuv в С++20 корутины, кажется.

на гитхабе с десяток тех оберток вокруг libuv которые дают возможностью юзать co_await итд соцпрограммы из С++17+

но все же asio VS libuv
лучше asio
хотя некоторые плюсики и у в libuv есть
Re[3]: Асинхронщина
От: B0FEE664  
Дата: 09.12.25 18:51
Оценка:
Здравствуйте, sergii.p, Вы писали:

BFE>>std::future, std::async

SP>это же создаст сонм потоков-бездельников

Или не создаст.
Зависит от имплементации.
Имплементация может использовать thread pool.
Так как у меня задачи совсем не укладываются в описанный топикастером подход, то на практике я эти функции не использовал, но, теоретически, как мне кажется, связка future + async укладывается в описанную задачу, как я её понял.
И каждый день — без права на ошибку...
Re[6]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 11.12.25 20:38
Оценка:
Здравствуйте, Великий Мессия, Вы писали:

ВМ>на гитхабе с десяток тех оберток вокруг libuv которые дают возможностью юзать co_await итд соцпрограммы из С++17+


А как они это делают в С++17, можете идею подкинуть?

ВМ>но все же asio VS libuv

ВМ>лучше asio

Чем лучше то, я во смотрю смотрю и не пойму? У меня не только стандартные протоколы и разные устройства. Я почти уверен что придется свои расширения и поддержку писать. Насколько сложно это будет с asio ?

ВМ>хотя некоторые плюсики и у в libuv есть

У меня есть специфические IO, такие как например STD handles или Unix Sockets, Serial Ports и т.д. Всё это поддерживается сейчас без библиотек своими силами под Windows и Linux. Преимущество, для меня, в том, что я могу добавить/расширить любой функционал. Снаружи это все обернуто в одни и те же интерфейсы (кстати похоже на asio получилось) и взаимозаменяемо. Вопрос в том, смогу ли я это всё контролировать в рамках asio ? Например таймауты connect и accept или скорость serial порта. Вообще, насколько asio расширяем?
Отредактировано 11.12.2025 20:40 Videoman . Предыдущая версия .
Re[2]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 11.12.25 20:46
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>std::future, std::async

BFE>

Это же руками закатывать солнце. Я хочу выпрямить логику. У меня реализована поддержка signal slots, куда можно любые объекты передавать и что угодно вызывать в каком угодно потоке. Ручной подход работает, но только если это подобие посылки сообщений. А вот если по сути логика линейная, но в ней есть много IO ожиданий, то это быстро превращается в программирование огромного автомата руками. Поэтому я и спрашиваю совета, хочу понять что-то поменялось/появилось в С++ для таких задач?
Отредактировано 11.12.2025 20:51 Videoman . Предыдущая версия .
Re[4]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 11.12.25 20:49
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Или не создаст.

BFE>Зависит от имплементации.
BFE>Имплементация может использовать thread pool.
BFE>Так как у меня задачи совсем не укладываются в описанный топикастером подход, то на практике я эти функции не использовал, но, теоретически, как мне кажется, связка future + async укладывается в описанную задачу, как я её понял.

У меня есть пулы потоков и сами потоки можно удобно использовать, и мне кажется std::async не гибким совсем. Он не позволяет убрать под капот логику ожидания длительных операций.
Re[7]: Асинхронщина
От: Великий Мессия google
Дата: 11.12.25 22:35
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

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


ВМ>>на гитхабе с десяток тех оберток вокруг libuv которые дают возможностью юзать co_await итд соцпрограммы из С++17+


V>А как они это делают в С++17, можете идею подкинуть?


С++17 co_await
https://godbolt.org/z/PvrKMsro5

так же как и в С++20
насколько я помню, и автор asio подтверждает
саму машинерию co_* добавили в компили с С++17, https://www.youtube.com/watch?v=proxLbvHGEQ
а некоторые await-ы в експерементал папочку
https://github.com/chriskohlhoff/asio/blob/55684d42ac00021b4c31ba8571aca414c863ead5/configure.ac#L166

AC_MSG_CHECKING([whether C++17 is enabled])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[[#if __cplusplus < 201703 L]]
[[#error C++17 not available]]
[#endif]])],
[AC_MSG_RESULT([yes])
HAVE_CXX17=yes;],
[AC_MSG_RESULT([no])
HAVE_CXX17=no;])


https://github.com/chriskohlhoff/asio/blob/55684d42ac00021b4c31ba8571aca414c863ead5/configure.ac#L194

AC_MSG_CHECKING([whether coroutines are enabled])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[[#if defined(__clang__)]]
[[# if (__clang_major__ >= 14)]]
[[# if (__cplusplus >= 202002) && (__cpp_impl_coroutine >= 201902)]]
[[# if __has_include(<coroutine>)]]
[[# define ASIO_HAS_CO_AWAIT 1]]
[[# endif]]
[[# elif (__cplusplus >= 201703) && (__cpp_coroutines >= 201703)]]
[[# if __has_include(<experimental/coroutine>)]]
[[# define ASIO_HAS_CO_AWAIT 1]]
[[# endif]]
[[# endif]]
[[# else]]
[[# if (__cplusplus >= 201703) && (__cpp_coroutines >= 201703)]]
[[# if __has_include(<experimental/coroutine>)]]
[[# define ASIO_HAS_CO_AWAIT 1]]
[[# endif]]
[[# endif]]
[[# endif]]
[[#elif defined(__GNUC__)]]
[[# if (__cplusplus >= 201709) && (__cpp_impl_coroutine >= 201902)]]
[[# if __has_include(<coroutine>)]]
[[# define ASIO_HAS_CO_AWAIT 1]]
[[# endif]]
[[# endif]]
[#endif]]
[[#ifndef ASIO_HAS_CO_AWAIT]]
[[# error coroutines not available]]
[#endif]])],
[AC_MSG_RESULT([yes])
HAVE_COROUTINES=yes;],
[AC_MSG_RESULT([no])
HAVE_COROUTINES=no;])


ВМ>>но все же asio VS libuv

ВМ>>лучше asio

V>Чем лучше то, я во смотрю смотрю и не пойму? У меня не только стандартные протоколы и разные устройства. Я почти уверен что придется свои расширения и поддержку писать. Насколько сложно это будет с asio ?


libuv СИ шная либа, по умолчанию будешь юзать колбеки, а для использования корутин аля co_*, нужно будет гитхабить обертки

asio С++ шная либа, там по умолчанию поддерживаются уже на выбор,или колбеки или лямюбды или co_*

что ты там собираешься писать я хз

ВМ>>хотя некоторые плюсики и у в libuv есть

V>У меня есть специфические IO, такие как например STD handles или Unix Sockets, Serial Ports и т.д. Всё это поддерживается сейчас без библиотек своими силами под Windows и Linux. Преимущество, для меня, в том, что я могу добавить/расширить любой функционал. Снаружи это все обернуто в одни и те же интерфейсы (кстати похоже на asio получилось) и взаимозаменяемо. Вопрос в том, смогу ли я это всё контролировать в рамках asio ? Например таймауты connect и accept или скорость serial порта. Вообще, насколько asio расширяем?

это все как я понимаю есть уже в asio

кстати минусы libuv, там подняли требования винды до win10+
там что win7,win8,vista не подержатся
можно ручками подправить либу
но некоторые функции отвалятся
Re[5]: Асинхронщина
От: B0FEE664  
Дата: 12.12.25 15:41
Оценка:
Здравствуйте, Videoman, Вы писали:

V> Он не позволяет убрать под капот логику ожидания длительных операций.


Я тут подумал... (со мной такое иногда бывает)
Действительно, при ожидании длительных логика может очень запутываться.
Надо это как-то организовать.
Применим объектный подход.
Так как с прикладной точки зрения основная операция при асинхронном вызове — это операция ожидания, то и создадим объект ожидания, назавём его WaitingBlock.
WaitingBlock должен уметь:

1) начать что-то делать
2) ждать окончания или таймаута
3) по таймауту выполнить одну операцию
4) по окончанию выполнить другую операцию.

выглядеть это должно как-то так:
auto fnConnect    = [addr=strUrl]() {  return ConnectTo(addr); };
auto fnOnOk       = [addr=strUrl](ConnectResult result) {  return DoSamthing(addr); };
auto fnOnTimeout  = [addr=strUrl]() {  std::cout << "timeout"; };

WaitingBlock doWork   (std::chrono::seconds(432), fnSendData, fnClose, fnOnTimeout);
WaitingBlock doConnect(std::chrono::seconds(132), fnConnect,  doWork,  fnOnTimeout);


Это один из подходов.

Другой подход я написал этой осенью.
Этой осенью я написал ftp клиента, который работает по автоматическому сценарию.
Сценарий задаётся в виде последовательности действий ActConnect, ActUser, ActPassword, ActFileDownload, ActFileUpload...
Каждое действие это объект, который, понятно, выполняет одну асинхронную операцию или является специальным, для условных или безусловных переходов.
Есть специальный объект Scenario, в котором задаётся последовательность операций.
При этом, по мере своего выполнения, сам сценарий может изменяться изнутри операции: добавлять или пропускать операции по выгрузке или загрузки файлов.
Но такой подход не использует никаких особых новшеств языка и мог быть написан много лет тому назад.
Так что мне не понятен причём тут C++17 или C++20
И каждый день — без права на ошибку...
Re[6]: Асинхронщина
От: Videoman Россия https://hts.tv/
Дата: 13.12.25 11:23
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>...


Твой WaitingBlock сейчас везде называется Promise. Это не проблема и есть уже давно. Смотри пара std::promise/std::future. Проблема же не в отложенном ожидании как таковом, а в восстановлении всего контекста ожидания, что бы продолжить бизнес логику дальше, с того же места, где она прервалась на ожидание.

Вот пример:
result class::some_method1(params1_t... params)
{
    Что создаем на стеке (1)
    Что создаем на стеке (2)
    Что создаем на стеке (3)

    wair_for_io_operation (1) 
    wair_for_io_operation (2) 

    Что создаем на стеке (4)
    Что создаем на стеке (5)
    Что создаем на стеке (6)

    wair_for_io_operation (3) 
    wair_for_io_operation (4)

    Очищаем созданное на стеке (1)
    Очищаем созданное на стеке (2)
    Очищаем созданное на стеке (3)
    Очищаем созданное на стеке (4)
    Очищаем созданное на стеке (5)
    Очищаем созданное на стеке (6)
}

result class::some_method2(params2_t... params)
{
    Что создаем на стеке (1)
    Что создаем на стеке (2)
    Что создаем на стеке (3)

    wair_for_io_operation (1) 
    wair_for_io_operation (2) 

    Что создаем на стеке (4)
    Что создаем на стеке (5)
    Что создаем на стеке (6)

    wair_for_io_operation (3) 
    wair_for_io_operation (4)

    Очищаем созданное на стеке (1)
    Очищаем созданное на стеке (2)
    Очищаем созданное на стеке (3)
    Очищаем созданное на стеке (4)
    Очищаем созданное на стеке (5)
    Очищаем созданное на стеке (6)
}
Нужно иметь возможность выполнять эти два метода параллельно, в зависимости от того, как завершаются операции ввода-вывода. В С++20 корутины между вызовами сохраняют объекты выделенные на стеке в куче и после ожидания заново копируют на стек в том же состоянии, для продолжения выполнения с прерванной точки. Как такое можно сделать на С++17 по твоему?
Re[7]: Асинхронщина
От: kov_serg Россия  
Дата: 13.12.25 15:43
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Твой WaitingBlock сейчас везде называется Promise. Это не проблема и есть уже давно. Смотри пара std::promise/std::future. Проблема же не в отложенном ожидании как таковом, а в восстановлении всего контекста ожидания, что бы продолжить бизнес логику дальше, с того же места, где она прервалась на ожидание.


V>Вот пример:
V>result class::some_method1(params1_t... params)
V>{
V>    Что создаем на стеке (1)
...
V>    Очищаем созданное на стеке (6)
V>}
V>
Нужно иметь возможность выполнять эти два метода параллельно, в зависимости от того, как завершаются операции ввода-вывода. В С++20 корутины между вызовами сохраняют объекты выделенные на стеке в куче и после ожидания заново копируют на стек в том же состоянии, для продолжения выполнения с прерванной точки. Как такое можно сделать на С++17 по твоему?


Всё гараздо проще. Нужно иметь возможность использовать разные стеки. В c++ таких механизмов не заложено. В виде костыля это сделано есть в корутинах.
В C++ такое сделать можно только не красиво и через ... вобщем как обычно принято. Если хочется красиво erlang или накрайняк java green threads
Re[7]: Асинхронщина
От: landerhigh Пират  
Дата: 16.12.25 11:19
Оценка:
Здравствуйте, Videoman, Вы писали:

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


V>[/ccode] Нужно иметь возможность выполнять эти два метода параллельно, в зависимости от того, как завершаются операции ввода-вывода. В С++20 корутины между вызовами сохраняют объекты выделенные на стеке в куче и после ожидания заново копируют на стек в том же состоянии, для продолжения выполнения с прерванной точки. Как такое можно сделать на С++17 по твоему?


boost::coroutine (aka boost::context).

Хоть на С++11
Re[7]: Асинхронщина
От: B0FEE664  
Дата: 19.12.25 19:33
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Твой WaitingBlock сейчас везде называется Promise. Это не проблема и есть уже давно. Смотри пара std::promise/std::future. Проблема же не в отложенном ожидании как таковом, а в восстановлении всего контекста ожидания, что бы продолжить бизнес логику дальше, с того же места, где она прервалась на ожидание.


Не-а. promise/future — это примитивы. Если их прямо использовать, то понять что-либо будет несколько затруднительно. Поэтому я и предлагаю сделать обвязку в виде функциональных объектов. Комбинация таких объектов позволит организовывать код в простую и понятную последовательность.

  Скрытый текст
V>Вот пример:
V>result class::some_method1(params1_t... params)
V>{
V>    Что создаем на стеке (1)
V>    Что создаем на стеке (2)
V>    Что создаем на стеке (3)

V>    wair_for_io_operation (1) 
V>    wair_for_io_operation (2) 

V>    Что создаем на стеке (4)
V>    Что создаем на стеке (5)
V>    Что создаем на стеке (6)

V>    wair_for_io_operation (3) 
V>    wair_for_io_operation (4)

V>    Очищаем созданное на стеке (1)
V>    Очищаем созданное на стеке (2)
V>    Очищаем созданное на стеке (3)
V>    Очищаем созданное на стеке (4)
V>    Очищаем созданное на стеке (5)
V>    Очищаем созданное на стеке (6)
V>}

V>result class::some_method2(params2_t... params)
V>{
V>    Что создаем на стеке (1)
V>    Что создаем на стеке (2)
V>    Что создаем на стеке (3)

V>    wair_for_io_operation (1) 
V>    wair_for_io_operation (2) 

V>    Что создаем на стеке (4)
V>    Что создаем на стеке (5)
V>    Что создаем на стеке (6)

V>    wair_for_io_operation (3) 
V>    wair_for_io_operation (4)

V>    Очищаем созданное на стеке (1)
V>    Очищаем созданное на стеке (2)
V>    Очищаем созданное на стеке (3)
V>    Очищаем созданное на стеке (4)
V>    Очищаем созданное на стеке (5)
V>    Очищаем созданное на стеке (6)
V>}
V>
Нужно иметь возможность выполнять эти два метода параллельно, в зависимости от того, как завершаются операции ввода-вывода. В С++20 корутины между вызовами сохраняют объекты выделенные на стеке в куче и после ожидания заново копируют на стек в том же состоянии, для продолжения выполнения с прерванной точки. Как такое можно сделать на С++17 по твоему?

Насколько я понимаю, корутины в С++20 достаточно бесполезны. Нужно сразу С++23, в котором являются объекты, позволяющие нормально организовать код. Честно говоря, я не понимаю зачем понадобилось вводить ключевые слова, вроде co_yield. Для совмещения параллельности выполнения с ленивыми вычислениями мне это кажется излишним.

Вот если мы создали какой-то ленивый объект. Он хранит одно состояние и умеет переходить в следующее при выдаче результата. Хорошо. Очевидно, что если внутри этого объекта реализовать некое вычисление следующего состояния параллельно к работе основного потока, то мы получим сопрограмму. Чем это принципиально отличается от корутин — Зачем нужны co_await с co_return'ами —

Вот если посмотреть на тот псевдокод, что вы написали, то можно заметить, что он написан исключительно в процедурном стиле, здесь создаём, тут ждем, там запускаем. А где место объекту? Если же начать думать в парадигме объектов, то всё станет намного проще. Собственно, не спроста в C++23 пришли std::generator. Это закономерный и, к моему сожалению, эволюционный путь развития.
И каждый день — без права на ошибку...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.