Как работает epoll?

Слово epoll сейчас определенно на слуху, в первую очередь благодаря росту популярности неблокирующих HTTP-серверов. При этом мало кто пытается разобраться в том, что, собственно, за ним стоит и почему использующие этот механизм продукты, среди которых достойное место занимают, например, nginx, node.js и Tornado, так значительно выигрывают в производительности у ближайших альтернатив. Хотите копнуть глубже?

О чем пойдет речь?

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

Сразу хочу предупредить, хоть на практике epoll и используется чаще, существуют и альтернативные реализации схожего подхода, например kqueue в BSD системах. Конечные продукты обычно используют библиотеку, абстрагирующуе низкоуровневые вызовы, наиболее распросраненные - libev и libevent.

Что это дает?

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

Обратная сторона медали

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

На пальцах

Вернемся к изначальному вопросу статьи: Как работает epoll? Давайте попробуем разобрать на простом примере.

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

Блокирующий (традиционный)

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

Вы ограничены как количеством печей, так и количеством помощников, которые могут поместиться в вашей пиццерии.

Неблокирующий (epoll и аналоги)

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

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

Заключение

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

Буду рад услышать дополнения и поправки в комментариях, до новых встреч!

17 февраля 2012 |  Иван Блинков  |  Linux