LINUX.ORG.RU

параллельный вызов accept

 


1

2

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



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

помню где-то читал что можно вызывать accept из нескольких потоков

Имеется в виду вызов accept из разных потоков на один и тот же файловый дескриптор?

yoghurt ★★★★★
()

Пишут, что в линуксе оно реинтрант, так что проблем быть не должно (правда пруфлинк на lxr у меня не открывается, а в новый код лезть лень).

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

yoghurt ★★★★★
()

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

там была использована схема такая:

1. создаётся и настраивается socket.

2. делается многократно fork()

3. что случается с родиетелем не помню :) , а каждый его дочерний процесс — делает accept() цикле.

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

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

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

ну родитель я так понимаю ловит sigchld и не в*ёбывается :) ну и закрывает свою копию прослушивающего сокета так как ему она без надобности

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

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

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

при всплесках активности поток вызывающий accept может быть узким местом

Если верить стэковерфлоу, там в ядре блокировка, они все будут узким местом :)

yoghurt ★★★★★
()

Если найдешь какие-то проблемы с этим в линуксе, заводи баг

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

смотри, когда accept слушает несколько потоков то при появлении нового соединения разблокируется 1 рандомный поток, если появляется 2 потока то разблокируется 2 рандомных потока если я правильно понимаю как оно работает, но по твоей ссылке получается что блокировка работает не на весь вызов а в определённых участках функции(как я понял в 2 местах), так вот если будет всплеск активности при одновременном вызове множеством потоков accept, то они отработают быстрее на отношение последовательного и параллельного кода в функции accept умноженного на количество потоков, при этом вырастет пинг для уже подключенных юзеров, если ты понял что я тут коряво написал и я прав в рассуждениях то появляется такой вопрос: что лучше?

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

Ты написал что-то странное, и я всё же придерживаюсь мнения, что _сам_ accept не будет работать быстрее, если ты будешь обращаться к нему из разных потоков.

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

С другой стороны, если ты организовал свои рабочие потоки так, что они все ждут следующего клиента accept-ом на одном и том же сокете, возможно, в этом и есть смысл ради относительной простоты реализации. Но раз уж тебя так заботит скорость, прочитай про цену переключения контекста, неблокирующий ввод-вывод, и epoll.

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

Грубо говоря так: есть функция из 3 строчек, в ней 2 строки могут выполнятся параллельно а 1 только последовательно( ну то есть если эта строка выполняется в одном потоке то другой ждёт её завершения в первом), если у нас 1 поток 2 раза вызовет эту функцию то будет так: выполняет 1 2 и 3 строка, и опять 1 2 и 3 строка. А если у нас два потока у нас выполняется за раз сразу 1 строка дважды, 2 строка дважды, и только 3 строка в разные периоды времени выполнится два раза. В первом случае с 1 потоком у нас 6 периодов времени займёт выполнение, а во втором случае 4 периода. Как-то так я рассуждаю :) в чём я не прав-то?

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

я знаю чего стоит переключение контекста(и на сколько я знаю в линукс оно почти такое же как и переключение контекста процесса, хотя хз в каких системах оно быстрее переключается) и потому использую poll в каждом потоке и эмпирически вычисляю оптимальное число юзеров на поток. Таким образом если число потоков равно числу потоков доступных системе проблем с переключением контекста быть не должно как я понимаю. А epoll актуален при большом числе наблюдаемых дескрипторов как пишут в man, вот только я не пойму при большом числе дескрипторов на конкретный вызов epoll или во всей системе.

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

Совершенно верно, не будет. Однако если твое приложение не просто зовет sleep в перерывах между epoll_wait, accept из разных тредов - стандартный способ задействовать простаивающие ядра.

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

а вот как посоветуешь определять простаивающие ядра? Ну вот я определил что под стандартной нагрузкой на потоке может работать 100 юзеров, и допустимый предел времени обработки всех юзеров за один проход не больше 10мс, имею я право в случае если скажем за последние 10 проходов по юзерам время обработки было сильно меньше этих 10мс включить вызов accept в poll? я просто хз какие тут техники есть

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

Зови accept только после того, как обслужишь все прочие дескрипторы при условии, что на обслуживание ушло не более Xms времени.

А по топику, в гугле поищи accept mutex. У ядра 2.6+ с этим проблем уже нет, но, тем не менее, существует дополнительно опция SO_REUSEPORT (довольно свежая), при использовании которой, якобы, соединения равномернее размазываются по тредам. На практике ни разу не сталкивался с необходимостью ее использования. Наверное, потому что у меня все задачи io-bound.

kawaii_neko ★★★★
()

Всегда можно вызывать accept из нескольких потоков.

может кто метнуть ссылочку на подобный материал?

posix

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

По личным замерам, вызов accept из одного процесса ничем не быстрее вызова из множества процессов/потоков. Скорости это не придает, зато зачем-то придется грузить другие ядра, вместо того, чтобы повесить accept'ор на одно ядро через cpu affinity, а остальные ядра смогут спокойно работать на оставшихся (это уж совсем сложный, «запущенный» случай :).

gh0stwizard ★★★★★
()

Accept можно вызывать из нескольких потоков, это вполне довольно стандартная схема для реализации preforked/prethreaded серверов. На некоторых древних платформах была проблема с тем, что при приходе нового соединения пробуждались все процессы которые ждут соединения на этом сокете, соединение при этом доставалось кому-то одному. Из-за этого для повышения эффективности делали блокировку вокруг accept чтобы только один поток делал accept, а остальные желающие ждали этот лок.

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

В многотредном приложении эффективнее делать accept в отдельном потоке, а не в основном io loop.

SO_REUSEPORT это не про то — опция дает возможность забиндиться на один порт разным процессам.

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

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

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

да и я опять не могу найти инфу по поводу - нужно ли переопределять структуру дескрипторов в poll в случае ошибки EINTR так же как и в select. Блин я ничего найти не могу, надо всё в избранное добавлять :(

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

http://lwn.net/Articles/542629/

The second of the traditional approaches used by multithreaded servers operating on a single port is to have all of the threads (or processes) perform an accept() call on a single listening socket...

The problem with this technique, as Tom pointed out, is that when multiple threads are waiting in the accept() call, wake-ups are not fair, so that, under high load, incoming connections may be distributed across threads in a very unbalanced fashion.

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

а ты не знаешь ответ на? :)

да и я опять не могу найти инфу по поводу - нужно ли переопределять структуру дескрипторов в poll в случае ошибки EINTR так же как и в select. Блин я ничего найти не могу, надо всё в избранное добавлять :(

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

man poll

RETURN VALUE

On success, a positive number is returned; this is the number of structures which have nonzero revents fields (in other words, those descriptors with events or errors reported). A value of 0 indicates that the call timed out and no file descriptors were ready. On error, -1 is returned, and errno is set appropriately.

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

и где тут ответ на мой вопрос? тоже и про селект написано, только если ещё больше искать можно найти инфу что в случае EINTR наборы дескрипторов то ли обнуляются то ли в неопр состоянии. А про поведение в таком случае с poll в твоей цитате ничего нет

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

Но если рассуждать логически то работа со структурой дескрипторов к poll не инкапсулирована, а значит все вносимые в нее изменения со стороны функции poll должны быть явно прописаны в man а там указано что только revent меняет значение, так что переопределять в случае ошибки не нужно ничего

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

Тут где-то написано об изменении структуры в случаях, отличных от retval > 0? Вообще, если читать man глазами, можно увидеть, что вызов ничего кроме revents не изменяет.

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

в селект тоже не написано, а если перейдёшь по ссылке которую я кинул выше написано, вот я и спрашиваю знает кто где явно написано что в случаях, отличных от retval > 0 функция не ломает структуры.

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

В Стивенсе же все это есть? И префорки с accept() в каждом процессе, и accept() в разных нитях. И асинхронный io (хотя сейчас лучше делать не вручную, а с libevent)

Вот эта книжка http://www.ozon.ru/context/detail/id/2881910/ , вроде должна легко находиться в электронном виде

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

да знаю эту книжку, читал, теперь вспомнил где я про accept видел )) а как в ней асинхронный io назывался? я не помню такого, может ты имел ввиду пакетный режим? Признаюсь я её не всю читал

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

Глава 6. Мультиплексирование ввода-вывода: функции select и poll

Глава 16. Неблокируемый ввод-вывод

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

кстати до сих пор не могу понять почему у стивенса написано что потоки разделяют переменную errno она ведь у каждого своя

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

да я знаю что он умер, но 3 переиздание 2006 или 7 года, чем они тогда занимались когда переиздавали? Циферки на обложке меняли?

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

Ну вот смотри, я согласен что вызов accept из всех рабочих потоков менее эффективен( незначительно ), но

Разница хорошо заметна на протоколах вроде http и большом количестве активных сокетов которые часто переоткрываются. Accept в event loop заметно просаживается по latency когда есть много других событий на обработку. Accept в отдельном треде работает мгновенно и позволяет клиенту заслать запрос в буфер сокета, а сервер в это время может спокойно заниматься обработкой других событий и передачей сокета из acceptor thread в event loop.

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

серверам на блокирующем io это не грозит, а на неблокирующем вводе-выводе я лично за отдельный тред для accept

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

ну в моей текущей задаче множество активных соединений которые не переустанавливают соединения (во всяком случае часто). А вообще у меня такая схема: есть два типа потоков - 1) отвечает за работу с сокетами. и 2) Обрабатывает сами запросы. В зависимости от задачи выполняемой сервером можно регулировать отношение этих потоков, скажем если обработка запросов на много быстрее выполняется чем работа с сокетами то на 1 поток обработки может быть несколько потоков работающих с сокетами (в текущих задачах так всегда и бывает) верно и обратное. Таким образом сервер может продолжать выполнять уже полученные события и события от других потоков. Это как я понимаю частично нивелирует то о чём ты пишешь, и во вторых если под обработкой понимать транзакции с бд ( что часто так и бывает) то вынесение их в отдельный поток тоже не плохая идея я думаю. Но в целом видимо да, ты всё-таки прав, но теперь когда я придумал более эффективную схему а поддерживать эту пока ещё надо, я не буду ничего менять в этой. В общем спасибо за дельные советы

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

да я почти всегда использую неблокирующий io

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

У меня конструкция вышла в итоге такая:

  • Acceptor thread, один
  • Reactor'ы, которые занимаются исключительно вводом выводом (event loop). По одному на CPU, но не больше 32. Reactor обсуживает события ввода-вывода плюс обрабатывает очередь задач которые ему положили другие треды
  • Thread pool для работы с внешними сервисами, вычислений и других долгих операций не связанных с основным IO. Настраивается, обычно 128-256 тредов

В целом конструкция оказалась вполне рабочая, единственное что единый thread pool для тяжелых задач оказался не очень хорошей идеей, лучше иметь разные для разных типов задач.

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

ну вообще на моё похоже, только у меня все Reactor'ы являются acceptor'ами. А почему ты выставил такое ограничение по числу потоков? Просто в большинстве задач есть события для которых достаточно только реакторов, ну грубо говоря пользователь А хочет написать пользователю Б и он знает на каком реакторе пользователь Б, для этого Thread pool не нужен но нагрузка на сами реакторы возрастает таким образом. Кстати опять не могу найти (карма видать у меня такая) где-то видел статью в которой написаны многие варианты организации сервера с плюсами и минусами, там был и просто однопоточный сервер с select и его плюсы минусы, и потом так всё усложняли усложняли до собсна наших с тобой схем, там примерно 7 или больше вариантов сервера, может знаешь подобные статьи? Мне почему-то кажется что на опеннет такая была, но я не могу найти(

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

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

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

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

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

У ядра есть своя «очередь» на прием входящих соединений (на ее длину для конкретного сокета влияет параметр backlog в listen), поэтому клиенты получат свой SYN,ACK до того, как сервер соизволит сделать accept. Лично я зову accept во всех потоках до EAGAIN и чувствую себя прекрасно.

kawaii_neko ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.