LINUX.ORG.RU

Асинхронная обработка tcp запросов

 ,


0

3

Всем привет.

Для того, чтобы принимать запросы пользователей в сокет и дальше читать и писать в сокет можно использовать современный liburing.

Всё просто, если пишем эхо сервер. А если на формирование ответа требуется время (сходить в базу данных или открыть большой файл или сделать запрос по сети к другому сервису), то хотелось бы делать это асинхронно.

Один из вариантов это использовать потоки. Сторонние библиотеки использовать не хочется (libuv или libev).

Кроме потоков есть ещё варианты?

Кроме потоков есть ещё варианты?

Конечно, и это процессы. Каждый пользовательский запрос к вэб-серверу можно обрабатывать в своём процессе.

По моему мнению, написание собственного вэб-сервера без использования программных библиотек это лучшее закрепление знаний по изучению нового для себя языка программирования в 2025 году. Почему? - Вычислительные сети вокруг нас и умение принимать, передавать и обрабатывать сетевые данные позволит быстро найти себе работу по душе. Да и знание текстовой разметки интернет-страниц тоже лишним не будет.

Enthusiast ★★★
()

Кроме потоков есть ещё варианты?

Конечно лучше делать поток на каждое соединение, потому что если все соединения делать скажем в одном потоке, то они будут тормозить друг друга. Даже если у потоков будет одновременный доступ к общим ресурсам, то при высокой нагрузке всё равно будет увеличение производительности хотя бы в 2 раза нежели в однопоточной системе. Писал HTTP-сервер на основе libmicrohttpd и бенчмаркал оба варианта. Если нет требования к оптимизации и скорости, можно делать в одном потоке, в разных потоках гораздо сложнее, потому что нужно делать синхронизацию.

Skullnet ★★★★☆
()
Последнее исправление: Skullnet (всего исправлений: 2)

Thread/process pool (это не то же самое что спавнить по потоку на соединение), async (однопоточный, попроще), async + work stealing scheduler (многопоточный, сложный). Возможно сам uring что-то поменяет в этом сложившемся ландшафте.

neumond
()
Ответ на: комментарий от alysnix

не доверяю я чату, уже работал с ним, он может ошибиться и потом признать свою ошибку

в общем я буду юзать liburing для I/O, а для формирования ответа буду юзать потоки…пока не вижу других приемлемых вариантов

Nazar22
() автор топика

сходить в базу данных или открыть большой файл или сделать запрос по сети к другому сервис

все эти операции можно делать асинхронно

Один из вариантов это использовать потоки

если есть блокирующий i/o их добавляют в треды, либо уносят в другой сервис и выставляют API (микросервисы), либо IPC

gagarin0
()
Последнее исправление: gagarin0 (всего исправлений: 1)

Один из вариантов это использовать потоки.

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

pathfinder ★★★★
()
Последнее исправление: pathfinder (всего исправлений: 1)
Ответ на: комментарий от Nazar22

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

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

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

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

ps. то что описано, является по сути «микросервисной» архитектурой только внутри процесса. в дальнейшем (если код написано красиво) легко масштабируется на настоящие микросервисы (каждая группа воркеров это по сути микросервис).

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от Rost

при использовании event loop очередь клиентов будет ждать, пока идёт обработка запроса текущего клиента. если обработка долгая, то у клиентов задержка получения ответа

Nazar22
() автор топика
Ответ на: комментарий от bugfixer

Не удержался. Прелесть какая :) И много мы одновременных соединений обрабатывать собрались?

Пока в системе не закончатся ресурсы, но на каком-то этапе просто начнёт тормозить синхронизация.

Skullnet ★★★★☆
()
Ответ на: комментарий от Skullnet

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

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

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

А может быть как-то по другому? TCP - это и есть подключиться и обмениваться данными постоянно - и это в 95% случаев. Как я и написал, если требований к ресурсам нет, то делайте в одном потоке. Большая часть TCP серверов - multi-thread.

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

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

и при этом жрать память и ресурсы кернела

Так он будет в ожидании, либо будет жрать памяти и ресурсы? Вы определитесь сначала.

Skullnet ★★★★☆
()
Последнее исправление: Skullnet (всего исправлений: 1)
Ответ на: комментарий от Skullnet

Так он будет в ожидании, либо будет жрать памяти и ресурсы? Вы определитесь сначала.

у треда есть стек ващета. и системная инфа в кернеле. даже если он спит на мьютексе. и число тредов ограничено системой. создание треда и его удаление, тоже время не маленькое.

TCP - это и есть подключиться и обмениваться данными постоянно - и это в 95% случаев.

как раз в природе больше сервисов, что работают в стиле «вопрос-ответ». и тут надо делать в стиле воркеров, а не тредов, и иметь воркер на запрос, а не тред на соединение.

«тред на соединение» - это для очень сильной связи по данным и минимальных задержек, для случаев что я уже привел. и такие случаи - по пальцам пересчитать.

alysnix ★★★
()

Один из вариантов это использовать потоки. Сторонние библиотеки использовать не хочется (libuv или libev).

Кроме потоков есть ещё варианты?

Ты не упомянул язык. Если C, то звиняй - в язык асинхронщину не завезли. Туда и языка-то не завезли. Поэтому потоки, процессы, сторонние библиотеки, или написать свою libuv, удачи с этим.

Правильный вариант - взять rust и tokio где уже всё есть.

anonymous
()
Ответ на: комментарий от anonymous

Если C, то звиняй - в язык асинхронщину не завезли.

https://github.com/tokio-rs/tokio/blob/817fa605ee6a2549fe8e6057ec23a8309d42d2e9/tokio/src/lib.rs#L25-L36

Tokio is an event-driven, non-blocking I/O platform for writing asynchronous
//! applications with the Rust programming language. At a high level, it
//! provides a few major components:
//!
....
//! * A [runtime] for executing asynchronous code, including a task scheduler,
//!   an I/O driver backed by the operating system's event queue (`epoll`, `kqueue`,
//!   `IOCP`, etc...), and a high performance timer.
gagarin0
()
Последнее исправление: gagarin0 (всего исправлений: 1)
Ответ на: комментарий от alysnix

у треда есть стек ващета. и системная инфа в кернеле. даже если он спит на мьютексе.

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

как раз в природе больше сервисов, что работают в стиле «вопрос-ответ». и тут надо делать в стиле воркеров, а не тредов

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

Skullnet ★★★★☆
()
Последнее исправление: Skullnet (всего исправлений: 1)
Ответ на: комментарий от Skullnet

Вот тебе нужно, например, передавать картинки

мне картинки передавать не нужно. это экзотическая задача.

сделай на однопоточном сервере и на многопоточном и сравни производительность.

я вообще на однопоточном сервере ничего делать не собираюсь. однопоток для демок.

alysnix ★★★
()

Варианты следующие:

  1. Thread per connection - это для blocking IO, устаревший подход.
  2. Thread per request - для non-blocking IO, хорошо себя показывает, если у тебя CPU-bound задачи. Испльзуется, например, в nginx, tomcat и jetty.
  3. Event loop - это когда у тебя мощнейшие сервера с отдельным CPU core под набор коннектов. Хорошо для IO-bound тредов. Используется в node.js и zuul.
anonymous
()
Ответ на: комментарий от alysnix

мне картинки передавать не нужно. это экзотическая задача.

Я картинки как пример привёл, если у тебя вывод «хеллоу ворлд» на запрос по сети, то да, тебе потоки не нужны.

Skullnet ★★★★☆
()
Ответ на: комментарий от Skullnet

ты не читаешь, что я пишу? я говорю о воркерах(группах тредов обслуживающих задания из очереди) и микросервисах, а не однотреде или «треде на соединение».

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

что ты мне вменяешь какой-то хелловорд?

alysnix ★★★
()
Ответ на: комментарий от alysnix

ты не читаешь, что я пишу? я говорю о воркерах(группах тредов обслуживающих задания из очереди) и микросервисах

Ты мне, кажется, вообще не понимаешь о чём пишешь и я это прекрасно вижу. Писал бы TCP/UDP сервера не нёс бы эту дичь.

Skullnet ★★★★☆
()
Ответ на: комментарий от Skullnet

Прям так сильно жрать ресурсы будет, ага.

Таки да.

Как раз и делают коммуникационный тред на соединение, а тред соединения уже спаунит нужный тред-воркер.

Вот неугомонный :) Да перестаньте уже фантазировать и нас веселить - никто так не делает. У нормальных людей тредов не сильно больше чем ядер на машинке. И latency вы на своём постоянном щёлканьи контекстами тоже выкинете на помойку. Не говоря уже о том что вы умрёте их создавать и останавливать постоянно.

bugfixer ★★★★★
()
Ответ на: комментарий от Nazar22

при использовании event loop очередь клиентов будет ждать, пока идёт обработка запроса текущего клиента. если обработка долгая, то у клиентов задержка получения ответа

При использовании event loop, тред-обработчик не блокируется, а дергает non-blocking вызовы (колбеки, промисы). Если, к примеру, у базы данных нет асинхронного драйвера и надо обязательно лочиться, берется отдельный тредпул и тред с IO дергается уже оттуда. Сам event loop таким образом не блокируетс никогда, это его фишка.

anonymous
()
Ответ на: комментарий от bugfixer

Фантазируешь тут ты, а я тебе говорю как делают взрослые дяди. Не веришь, открой htop и посмотри как работает тот же apache, у которого и есть тред на соединение (запрос) и тредпул из 250 доступных тредов для всего этого, а в non-threaded режиме просто спаунится новый процесс через fork.

Skullnet ★★★★☆
()
Последнее исправление: Skullnet (всего исправлений: 1)
Ответ на: комментарий от Skullnet

Фантазируешь тут ты, а я тебе говорю как делают взрослые дяди.

Эх, где моя молодость :) Только пионеры так делают, в своих влажных мечтах.

открой htop и посмотри как работает тот же apache

Т.е. свои сервера мы таки не писали. Открою вам секрет - apache абсолютно точно не форкает тред на каждое соединение.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Т.е. свои сервера мы таки не писали. Открою вам секрет - apache абсолютно точно не форкает тред на каждое соединение.

Клован, сходи почитай вики апача. Тебе там чёрным по белому написали как апач резервирует потоки для выполнения запросов и как создаёт child процессы через fork для выполнения запросов в «non-threaded» режиме.

Skullnet ★★★★☆
()
Последнее исправление: Skullnet (всего исправлений: 1)
Ответ на: комментарий от Skullnet

Тебе там чёрным по белому написали как апач резервирует потоки

В каком месте «резервирует» предполагает по потоку на соединение, умница вы наша? Полёт вашей фантазии и фонтан ваших идей перестаёт быть занимательным.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

В каком месте «резервирует» предполагает по потоку на соединение, умница вы наша?

Тебе в том числе написали почему делают именно так, а не создают потоки на лету. В общем, садись, два. На этом разговор окончен.

Skullnet ★★★★☆
()
Ответ на: комментарий от Skullnet

например, передавать картинки 500 людям одновременно

Не получится, хоть ты 100500 тредов создай. Доступ к диску последовательный с мультиплексированием. Даже если там какой-то мегарайд всё равно параллелизм будет измеряться десятками потоков, но никак не 500.

Что касается апача, то там дохрена потоков создаётся потому что предполагается связка с пыхом или другой скриптотой, которая крутит VM чисто на CPU пока идёт IO в каком-то потоке. Для Си это не актуально.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Не получится, хоть ты 100500 тредов создай. Доступ к диску последовательный с мультиплексированием.

Я и не говорю о создании 100500 тредов, просто в однопоточной системе у тебя на тормозных запросах будет пыхтеть одно ядро, а в многопоточной это всё можно раскидать по тредам - быстрее. Понятно что и у многопоточности есть предел, потому что нужна синхронизация при доступе к общим данным, я об этом сразу написал.

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

Тем более, это всё нужно делать при каждом запросе.

Skullnet ★★★★☆
()
Ответ на: комментарий от anonymous

о привет, а ты упорный

Если C, то звиняй - в язык асинхронщину не завезли.

я тебе скинул кусок комментариев, где tokio использует под собой (а что еще ему остается) syscall написанные на С для своей работы

но ты не смог проследить связь, по этому приходиться тебе пояснять на пальцах.

gagarin0
()
Последнее исправление: gagarin0 (всего исправлений: 1)

Если создать много (нативных) потоков, то система ляжет. Может, там солярис не ляжет, а вот линукс точно. Про винду мы тут скромно умолчим.

Поэтому придумали зеленые потоки и асинхронность.

Скажем, в хаскеле, есть нативные потоки и потоки, привязанные к системным (для того же GUI).

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

Теперь языки типа котлина и F#. Там ты создаешь асинхронное вычисление на каждое новое соединение. Концептуально, это как бы имитация зеленых потоков. Вид в анфас и профиль отличается, но суть примерна та же. С помощью скромных ресурсов компьютера сымитировать множество потоков исполнения. При этом количество нативных потоков будет ограниченным - система не ляжет.

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

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

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

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

Если у тебя до фига соединений, и есть желание повозиться с низкоуровневой кухней плюсов, то бери asio.

Если не хочешь особо заморачиваться деталями, то бери хаскель! Я не шучу, почти не шучу.

anonymous
()
Ответ на: комментарий от no-such-file

Не получится, хоть ты 100500 тредов создай. Доступ к диску последовательный с мультиплексированием. Даже если там какой-то мегарайд всё равно параллелизм будет измеряться десятками потоков, но никак не 500.

Так это и хорошо, что доступ к диску последовательный. Большинство потоков вэб-сервера будет находиться в состоянии ожидания ввода/вывода данных с диска и поэтому затраты планировщика операционной системы на переключение потоков будут малы - зашел, увидел «спячку», вышел.

Зато работает запросопоточный вэб-сервер просто: получил запрос по сети; создал новый поток; передал в него запрос пользователя и забыл об этом потоке. Как только поток обработает пользовательский запрос, то он самоуничтожится и освободит от себя и своих данных оперативную память.

Максимальное количество создаваемых потоков в секунду только ограничить и «зависшие» потоки изредка уничтожать и все. Отличное решение для многоядерных вычислителей, на мой взгляд.

Enthusiast ★★★
()
Ответ на: комментарий от anonymous

Поэтому придумали зеленые потоки и асинхронность.

«Зеленые» потоки это те же самые «позиксные» потоки «Линукса», которые создаются функцией «pthread_create»? Только какая-то обертка над ними или это какие-то самобытные потоки?

Enthusiast ★★★
()
Последнее исправление: Enthusiast (всего исправлений: 1)
Ответ на: комментарий от bugfixer

Просто posix треды SVR4 не смогли реализовать ни в bsd, ни в linux. На linux oops вообще ковылял - треды тогда весили почти как процессы, и места занимали столько же.

Shadow ★★★★★
()
Последнее исправление: Shadow (всего исправлений: 1)