Pull to refresh

Comments 16

Абсолютно нежизнеспособное решение по следующим причинам:


  1. Используется функция poll, во-первых, имеющая лимит в 1024 дескрипторов, а во-вторых, полный список этих дескрипторов передаётся в ОС каждый вызов, что приводит к сложности O(N^2). Почитайте про epoll, а ещё лучше — io_uring.


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


  3. В C++ имеется поддержка stackless корутин, так что никаких проблем с колбэками больше нет.


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

Почему же среди кодов ошибок есть:


EINVAL The nfds value exceeds the RLIMIT_NOFILE value.

?

Потому что процессу в принципе не получится открыть больше чем RLIMIT_NOFILE дескрипторов

Реальным решением является асинхронный ввод-вывод (поток запускает операцию, а по её завершении ОС устанавливает некое событие, которое может быть проверено потоком, и/или дёргает процедуру обратного вызова). Однако он не везде есть (стандартом POSIX предусмотрен -- но как необязательный; вот Винде всю жизнь был и есть, поскольку унаследован от VAX/VMS, а той -- от RSX-11). Неблокирующий ввод-вывод асинхронным в этом смысле не является: он не оповещает о моменте завершения операции, он лишь говорит о готовности начать новую операцию. Кроме того, где-то встречал, что, по крайней мере, линуховый poll для дисковых операций всегда возвращает готовность (если не прав -- поправьте, я с Линухом не работаю, поэтому не копался в его вызовах).

Кроме того, где-то встречал, что, по крайней мере, линуховый poll для дисковых операций всегда возвращает готовность

Да. Поэтому используйте io_uring.

Спасибо за наводку, почитаю на досуге. Помню, в своё время (конец 1980-х или самое начало 1990-х -- ещё при Союзе, в общем) дико плевался, поглядев на API Unix (какого-то из советских клонов) -- как раз из-за отсутствия асинхронщины. Производительность её, кстати, была ниже плинтуса по сравнению с RSX-11, когда дело доходило до интенсивных вызовов API, а памяти она жрала под себя выше крыши :) Обе системы гоняли на СМ-1420 -- написали тестовый набор программ, которые интенсивно взаимодействовали друг с другом, используя ОС, -- очевидно, что обычная расчётная задача, работающая "внутри себя", от эффективности ОС вообще никак не зависит, и хотелось проверить именно качества системы. Правда, что творилось в тесте для Unix, я не знаю, -- делал не я, моё дело было писать под RSX-11 (точней, под её советский клон -- ОС-РВ).

Но, в любом случае, отсутствие поддержки стандартных средств (POSIX в полном объёме), конечно, удручает...

Может какую-то статью напишете про то время? Интересно было бы почитать.

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

Мне кажется, что с асинхронным вводом-выводом код чего либо сложнее такого echo сервера будет нечитаем.

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

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

Зачем используетсяPOLLOUT? читаем man poll -Теперь запись возможна, но запись данных больше, чем доступно места в сокете или канале, по-прежнему приводит к блокировке.
Т.е. с большей вероятностью (вы же незнаете сколько и не проверяете это) у вас всё равно блокировка при записи. Ассинхронность сокетов это же когда запись/чтение раздельны, не проще ли тогда их разделить? А есть ли смысл делить чтение/запись tcp сокетов, когда формализация протокола скорей всего требует упорядочивания - прочитай х байт, прочитай еще y байт, а теперь запиши n байт.

Зачем вообще select/poll/epoll для одного fd? Их эффективность в обработке группы fd. Здесь разве не проще использовать O_NONBLOCK + обработка кода возврата read/write?

int client_fd = accept4(socket_fd, nullptr, nullptr, SOCK_NONBLOCK);

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

poll используется не для одного fd. Он используется для fd, на котором сервер принимает новые соединения и на fd клиентов. Но если клиентов нет, то да, fd будет один.

Sign up to leave a comment.

Articles