Комментарии 15
Интересна реализация epoll в многопотоке. Что происходит, когда работа с добавлением-удалением дескрипторов происходит одновременно из нескольких потоков? Что происходит при одновременном ожидании событий?
если все потоки работают с одним epoll fd, то
1.1 добавлять можно, но при добавлении уже существующего fd получишь EEXIST; можно копировать fd, например через dup(), и тогда добавлять от каждого потока по своему (интересно, правда, где такое можно применить);
1.2 с удалением, думаю, такя же история как и с добавлением;
2) при одновременном ожидании события, получит событие только один поток (процесс, если унаследовал epoll fd, например при форке).
Не-не, эту логику работы я знаю. Но речь идёт о деталях реализации epoll. Вот мне и интересны эти детали.
Например, имеет ли смысл балансировать нагрузку, работая с механизмом epoll из нескольких потоков? Или в ядре всё обложено мьютексами, и никакого преимущества по сравнению с работой из одного потока это не даст.
Не уверен, что nginx работает именно так. Насколько я понял, в каждом потоке — свой epoll, который не пересекается по дескрипторам с другими. А балансировка осуществляется на уровне accept и reuse_addr, т.е. просто создаётся несколько сокетов для прослушивания одного и того же порта.
преимущество должно быть, два лучше, чем один :)
хотя за деталями и сам сюда пришел. но пока не могу представить, в чем была бы затычка против «своего epoll», кроме того, что «первые» потоки будут работать чаще, пока евенты влезают в запрошенный объем, как следствие и делают «свой epoll».
Скорее всего, 1) Эффекты одновременных epoll_ctl могут применяться в любом порядке, тут никакой синхронизации, но воздействие на каждый дескриптор атомарно. 2) При готовности по epoll_wait будут разбужены все нити, которые успеют ухватить хотя бы по одному событию, но порядок формирования выходных списков не гарантирован, и может получиться какой угодно интерливинг.
В документации я что-то подобное читал, но так как тестировать такие вещи сложно, то шансы на баг ненулевые.
Но я вижу крайне мало смысла в таких испытаниях, потому, что даже с одной нитью есть масса проблем. Например, что будет, если для сокращения времени реакции за одну итерацию обрабатываются не все готовности, по каким-то дескрипторам в силу расположения звёзд нотификации будут приходить сильно чаще остальных, и будет перекос в результирующих приоритетах потоков данных? С этим пытаются бороться несколькими методами — в основном вокруг организации внутренней очереди уже по результатам отчёта epoll — и тут реализация на нескольких нитях, если это не редкое "я перегружена, забери клиентов себе", всё только усложнит.
Интересная вещь, что все вещи в линуксе стараются (пере)делать на файловые дескрипторы. Кроме epoll'а, о чём речь в этой статье, туда же перемещаются например таймеры (timerfd), семафоры (eventfd), сигналы (signalfd).
И это очень удобно. Вот в Windows есть IOCP, но таймеры и ивенты она не поддерживает, только сокеты и файлы, причём для сокетов и файлов используются разные вызовы.
Таймеры и события — это объекты ожидания, они не производят операции ввода‐вывода, им IOCP ни к чему.
Насколько знаю, порт завершения ввода‐вывода одинаково инициализируется для файлов и сокетов; на сокетах и файлах асинхронные операции запускаются одной и той же функцией ReadFile.
Ну это удобно просто для учёта и лимитирования, если сравнивать, например, с BSD kqueue. Там есть аналогичные возможности, но они сидят внутри реализации и ограничения на них значительно глобальнее и топорнее.
вот только сегодня появилась потребность об этом узнать, как случайно попался ваш перевод, дело нужное, спасибо.
пользуясь случаем, правильно ли понимаю, что нет способа самому пробудить сокет с конкретным типом евента? (некий "fire_event(ep, fd, EPOLLIN)" речь про пробуждение из собственного кода, а не когда удалённая сторона действительно пишет данные, или отключается)
пробовал по всякому, если писать в socket_fd(write/send), epollin не срабатывает.
хотя shutdown на сокете все же пробуждает его как epollrdhup.(мог ошибиться, словом, как евент закрытия сокета)
пользуясь случаем, правильно ли понимаю, что нет способа самому пробудить сокет с конкретным типом евента?
Большой встречный вопрос — а зачем? Мне сложно представить себе такую необходимость, особенно учитывая, что, если сокет в блокирующем режиме и вы сэмулируете фейковую готовность операции, код соответствующей операции тупо зависнет.
Если же надо пробудить весь комплект epoll в целом, то обычно используют сигнальный пайп, или, по-современному, eventfd.
Например, в качестве ивента можно передать 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
Реализация epoll, часть 1