Здравствуйте, Videoman, Вы писали:
N>>Если посмотреть на userver, который делает всё то, что тебе надо, то видно, что он C++17 требует. То есть его достаточно.
V>Мне показалось, что там во всю стекфул корутины используются. Подход понятен, но мне хотелось бы получить что-то в стиле node.js. Один основной поток, в котором логика строго однопоточная, без необходимости синхронизации. Мне показалось, что со стекфул корутинами, если мы используем сразу кучу ядер,
Именно что показалось.
Корутина — это не поток. Это просто возможность прервать выполнение функции и потом продолжить с того же места. Когда прервать и когда продолжить, решать программисту, т.е. тебе.
V>Уточню. У меня задача не запускать паралльно несколько независимых веток исполнения, а делать все по очереди (псевдопараллельно(, как будто в одном потоке, без синхронизации. Если на низком уровне рассматривать, похоже на большой автомат, но который каждый цикл выполняет одну и ту же логику, но она огромная размазана между кучи ожиданий IO, иногда вложенных. Похоже на то, как работает node.js. Ощущение, что безстековые корутины тут лучше подходят или я не прав?
Тут подходят безстековые корутины. И стековые тоже подходят.
Разница между ними в том, что в случае безстековых корутин сохранение контекста либо делается вручную, либо должно поддерживаться языком/средой (в случае 20х корутин это делает компилятор, как правило выделяя память на куче). Для стековых корутин должен выделяться отдельный стек, и запуск/выход из них связан с необходимостью сохранения и восстановления всех регистров, что относительно дорого. Из плюсов стековых корутин можно назвать то, что переключение контекста возможно из любой функции в стеке вызовов, при этом функции выше по стеку вообще могут не знать, что они выполняются в контексте корутины.
Здравствуйте, Videoman, Вы писали:
V>Большая просьба по возможности воздержаться от тыканья в готовые библиотеки, в плане бери вот это, там всё уже есть и не думай. Хочется понять основные идеи и подходы, а не максимально быстро начать писать код в продакшене.
Для ожидания события в сокете можно использовать poll/epoll/select/kqueue (сокет должен быть не блокируемым). Но кросс-платформенный из этого только select.
В кроссплатформенных библиотеках boost::asio, libevent и ещё нескольких именно эти механизмы и используются.
Можно коллбек прикрутить, можно в отдельном потоке ответ ждать, причём можно ждать ответ более, чем на одном сокете.
Здравствуйте, Videoman, Вы писали: V> ... кто как подходит к IO-bound задачам, где CPU, будем считать для простоты, почти не используется и в основном приходится ждать завершение операций.
Я бы просто запускал новый поток и ожидал сообщения от него о завершении обмена данными. Ядер у современных процессоров гораздо больше одного и в наши дни неразумно обрабатывать все задачи сопрограммами на одном ядре процессора вместо многопоточной, многоядерной обработки.
Здравствуйте, Pitirimov, Вы писали:
P>Здравствуйте, Videoman, Вы писали: V>> ... кто как подходит к IO-bound задачам, где CPU, будем считать для простоты, почти не используется и в основном приходится ждать завершение операций.
P>Я бы просто запускал новый поток и ожидал сообщения от него о завершении обмена данными. Ядер у современных процессоров гораздо больше одного и в наши дни неразумно обрабатывать все задачи сопрограммами на одном ядре процессора вместо многопоточной, многоядерной обработки.
Дык если потоков много очень большие накладные расходы по памяти и на переключение, да и кэш бодро вымывается.
Поэтому вместо миллиона тактов на переключения потока использоую сотни тактов на планировщик и вместа 4мб стека на поток мелкие стуктуры и coroutine-ы.
В результате выигрыш по производительности может быть очень ощутимым. Если бы он был не значительным миллионы мух не заморачивались бы.
Здравствуйте, Pitirimov, Вы писали:
P>Здравствуйте, Videoman, Вы писали: V>> ... кто как подходит к IO-bound задачам, где CPU, будем считать для простоты, почти не используется и в основном приходится ждать завершение операций.
P>Я бы просто запускал новый поток и ожидал сообщения от него о завершении обмена данными.
Довелось лет 5 назад понаблюдать за тем, как Linux вставал колом на такой модели thread per connection когда количество одновременно обслуживаемых соединений превышало 32K штук.
Здравствуйте, Pitirimov, Вы писали:
P>Я бы просто запускал новый поток и ожидал сообщения от него о завершении обмена данными. Ядер у современных процессоров гораздо больше одного и в наши дни неразумно обрабатывать все задачи сопрограммами на одном ядре процессора вместо многопоточной, многоядерной обработки.
В моих новых задачах мне такое не подходит. Говорю же CPU-bound. Основному потоку нечего желать, только логику распределять, всё на ожидании висит. Вот только автомат по логике получается безумный. Поэтому хочется всё это выпрямить. Зачем тут стрелять из пушки.
Здравствуйте, landerhigh, Вы писали:
L>Именно что показалось. L>Корутина — это не поток. Это просто возможность прервать выполнение функции и потом продолжить с того же места. Когда прервать и когда продолжить, решать программисту, т.е. тебе.
А я где-то написал что корутина это — поток . Корутина это не поток, но выполняется она не в вакуме, а в контексте потока ОС.
L>Тут подходят безстековые корутины. И стековые тоже подходят. L>Разница между ними в том, что в случае безстековых корутин сохранение контекста либо делается вручную, либо должно поддерживаться языком/средой (в случае 20х корутин это делает компилятор, как правило выделяя память на куче). Для стековых корутин должен выделяться отдельный стек, и запуск/выход из них связан с необходимостью сохранения и восстановления всех регистров, что относительно дорого. Из плюсов стековых корутин можно назвать то, что переключение контекста возможно из любой функции в стеке вызовов, при этом функции выше по стеку вообще могут не знать, что они выполняются в контексте корутины.
Я знаю чем стековые корутины отличаются от безстековых, только вот разница большая, на мой взгляд. Как я понимаю, в случае стековых корутин мне придется всё равно писать в многопоточном стиле, пофигу что физически поток один, нити то разные и стеки тоже. Потом придется как-то контекст руками размазывать по нитям, делать аналог join. Отличий от потоков почти никакой, с той лишь разницей, что мы оптимизируем переключение контекста оптимизирую по CPU, а мне это не нужно, хотелось бы логику выпрямить.
Меня интересуют безстековые корутины или их заменители, когда вся логика в основном потоке. Еще большой плюс, что можно использовать пул потоков, в которых выполнять асинхронные операции изолированно, а результат возвращать основному потоку. Видимо придется использовать 20-й стандарт.
А на 20-м стандарте есть какие-нибудь библиотеки, которые использую корутины оттуда? Была бы идеальна обертка libuv в С++20 корутины, кажется.
Здравствуйте, Videoman, Вы писали:
V>А на 20-м стандарте есть какие-нибудь библиотеки, которые использую корутины оттуда? Была бы идеальна обертка libuv в С++20 корутины, кажется.
Здравствуйте, Videoman, Вы писали:
V>Что в идеале хочется получить: V>
V>data = await read(...);
V>await write(data);
V>
V>Понятно, что скорее всего должен быть некий механизм, который следит за завершенными операциями и продолжает выполнение с точки ожидания. V>Как такое можно организовать проще всего? Обязательно нужно кроссплатформенное решение, без погружения в дебри с ассемблером, регистрами и т.л.
Здравствуйте, sergii.p, Вы писали:
BFE>>std::future, std::async SP>это же создаст сонм потоков-бездельников
Или не создаст.
Зависит от имплементации.
Имплементация может использовать thread pool.
Так как у меня задачи совсем не укладываются в описанный топикастером подход, то на практике я эти функции не использовал, но, теоретически, как мне кажется, связка future + async укладывается в описанную задачу, как я её понял.
Здравствуйте, Великий Мессия, Вы писали:
ВМ>на гитхабе с десяток тех оберток вокруг 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 расширяем?
Здравствуйте, B0FEE664, Вы писали:
BFE>std::future, std::async BFE>
Это же руками закатывать солнце. Я хочу выпрямить логику. У меня реализована поддержка signal slots, куда можно любые объекты передавать и что угодно вызывать в каком угодно потоке. Ручной подход работает, но только если это подобие посылки сообщений. А вот если по сути логика линейная, но в ней есть много IO ожиданий, то это быстро превращается в программирование огромного автомата руками. Поэтому я и спрашиваю совета, хочу понять что-то поменялось/появилось в С++ для таких задач?
Здравствуйте, B0FEE664, Вы писали:
BFE>Или не создаст. BFE>Зависит от имплементации. BFE>Имплементация может использовать thread pool. BFE>Так как у меня задачи совсем не укладываются в описанный топикастером подход, то на практике я эти функции не использовал, но, теоретически, как мне кажется, связка future + async укладывается в описанную задачу, как я её понял.
У меня есть пулы потоков и сами потоки можно удобно использовать, и мне кажется std::async не гибким совсем. Он не позволяет убрать под капот логику ожидания длительных операций.
Здравствуйте, Videoman, Вы писали:
V>Здравствуйте, Великий Мессия, Вы писали:
ВМ>>на гитхабе с десяток тех оберток вокруг libuv которые дают возможностью юзать co_await итд соцпрограммы из С++17+
V>А как они это делают в С++17, можете идею подкинуть?
ВМ>>но все же 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 не подержатся
можно ручками подправить либу
но некоторые функции отвалятся
Здравствуйте, Videoman, Вы писали:
V> Он не позволяет убрать под капот логику ожидания длительных операций.
Я тут подумал... (со мной такое иногда бывает)
Действительно, при ожидании длительных логика может очень запутываться.
Надо это как-то организовать.
Применим объектный подход.
Так как с прикладной точки зрения основная операция при асинхронном вызове — это операция ожидания, то и создадим объект ожидания, назавём его WaitingBlock.
WaitingBlock должен уметь:
1) начать что-то делать
2) ждать окончания или таймаута
3) по таймауту выполнить одну операцию
4) по окончанию выполнить другую операцию.
Другой подход я написал этой осенью.
Этой осенью я написал ftp клиента, который работает по автоматическому сценарию.
Сценарий задаётся в виде последовательности действий ActConnect, ActUser, ActPassword, ActFileDownload, ActFileUpload...
Каждое действие это объект, который, понятно, выполняет одну асинхронную операцию или является специальным, для условных или безусловных переходов.
Есть специальный объект Scenario, в котором задаётся последовательность операций.
При этом, по мере своего выполнения, сам сценарий может изменяться изнутри операции: добавлять или пропускать операции по выгрузке или загрузки файлов.
Но такой подход не использует никаких особых новшеств языка и мог быть написан много лет тому назад.
Так что мне не понятен причём тут C++17 или C++20
Твой 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 по твоему?
Здравствуйте, 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