Как стать автором
Обновить

Комментарии 15

Интересна реализация epoll в многопотоке. Что происходит, когда работа с добавлением-удалением дескрипторов происходит одновременно из нескольких потоков? Что происходит при одновременном ожидании событий?

Как я понял после прочтения man7.org/linux/man-pages/man7/epoll.7.html,

если все потоки работают с одним epoll fd, то

1.1 добавлять можно, но при добавлении уже существующего fd получишь EEXIST; можно копировать fd, например через dup(), и тогда добавлять от каждого потока по своему (интересно, правда, где такое можно применить);
1.2 с удалением, думаю, такя же история как и с добавлением;
2) при одновременном ожидании события, получит событие только один поток (процесс, если унаследовал epoll fd, например при форке).

Не-не, эту логику работы я знаю. Но речь идёт о деталях реализации epoll. Вот мне и интересны эти детали.


Например, имеет ли смысл балансировать нагрузку, работая с механизмом epoll из нескольких потоков? Или в ядре всё обложено мьютексами, и никакого преимущества по сравнению с работой из одного потока это не даст.

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

Не уверен, что nginx работает именно так. Насколько я понял, в каждом потоке — свой epoll, который не пересекается по дескрипторам с другими. А балансировка осуществляется на уровне accept и reuse_addr, т.е. просто создаётся несколько сокетов для прослушивания одного и того же порта.

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

Скорее всего, 1) Эффекты одновременных epoll_ctl могут применяться в любом порядке, тут никакой синхронизации, но воздействие на каждый дескриптор атомарно. 2) При готовности по epoll_wait будут разбужены все нити, которые успеют ухватить хотя бы по одному событию, но порядок формирования выходных списков не гарантирован, и может получиться какой угодно интерливинг.
В документации я что-то подобное читал, но так как тестировать такие вещи сложно, то шансы на баг ненулевые.


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

Интересная вещь, что все вещи в линуксе стараются (пере)делать на файловые дескрипторы. Кроме epoll'а, о чём речь в этой статье, туда же перемещаются например таймеры (timerfd), семафоры (eventfd), сигналы (signalfd).

И это очень удобно. Вот в Windows есть IOCP, но таймеры и ивенты она не поддерживает, только сокеты и файлы, причём для сокетов и файлов используются разные вызовы.

Таймеры и события — это объекты ожидания, они не производят операции ввода‐вывода, им IOCP ни к чему.


Насколько знаю, порт завершения ввода‐вывода одинаково инициализируется для файлов и сокетов; на сокетах и файлах асинхронные операции запускаются одной и той же функцией ReadFile.

Верно. Но для ConnectEx и AcceptEx это решение не универсально.

Ну это удобно просто для учёта и лимитирования, если сравнивать, например, с BSD kqueue. Там есть аналогичные возможности, но они сидят внутри реализации и ограничения на них значительно глобальнее и топорнее.

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


пользуясь случаем, правильно ли понимаю, что нет способа самому пробудить сокет с конкретным типом евента? (некий "fire_event(ep, fd, EPOLLIN)" речь про пробуждение из собственного кода, а не когда удалённая сторона действительно пишет данные, или отключается)
пробовал по всякому, если писать в socket_fd(write/send), epollin не срабатывает.
хотя shutdown на сокете все же пробуждает его как epollrdhup.(мог ошибиться, словом, как евент закрытия сокета)

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

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


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

Может быть epoll_ctl() с EPOLL_CTL_MOD сработает?
Например, в качестве ивента можно передать EPOLLIN, что значит — файловый дескриптор готов к чтению.

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);


man7.org/linux/man-pages/man2/epoll_ctl.2.html
Зарегистрируйтесь на Хабре, чтобы оставить комментарий