Погружение в libuv. Часть 1. Зачем он нужен?

Погружение в libuv. Часть 1. Зачем он нужен?


Anastasia Kotova

Введение

Как мы знаем, Node.js состоит не только из JavaScript-модулей, написанных специально для серверной разработки. Под капотом он использует две важнейшие составляющие: движок V8, отвечающий за быстрое выполнение JavaScript-кода, и библиотеку libuv, которая реализует Event Loop — но не только.

Насколько хорошо вы знакомы с libuv? В этом небольшом цикле статей мы разберём его основные компоненты, а заодно попробуем поработать с этой библиотекой на практике.

Дисклеймер: эти статьи написаны фронтенд-разработчиком, хорошо знакомым с JavaScript, но не столь уверенно чувствующим себя в C и C++. Поэтому libuv мы будем рассматривать довольно верхнеуровнево — чтобы лучше понимать принципы работы Node.js и не бояться заглядывать в его исходники, включая части, написанные на C и C++. Приятного чтения!

Как появился libuv

Когда речь заходит о Event Loop в Node.js, первым делом стоит вспомнить именно библиотеку libuv. Но откуда она взялась и зачем вообще была создана?

В цикле статей про Event Loop в Node.js мы уже обсуждали, чем привлекательна идея неблокирующего ввода-вывода. Если кратко: Node.js использует non-blocking I/O — подход, при котором операции выполняются асинхронно, и вместо ожидания их завершения поток может переключиться на другие задачи. Это позволяет, например, обрабатывать тысячи соединений одновременно, не создавая по потоку на каждое.

Чтобы реализовать такую модель, необходима поддержка неблокирующего I/O на уровне операционной системы — и, к счастью, она уже существует.

Неблокирующий I/O

Для корректной работы Event Loop необходимо, чтобы программа могла узнавать о завершении операций — например, что сокет готов к чтению — без блокировки потока.

Современные ОС предоставляют для этого специальные механизмы: epoll (Linux), kqueue (macOS) и IOCP (Windows). С их помощью программа может подписаться на события и получать уведомления, когда нужное условие выполнено — скажем, данные пришли на сокет или завершилось чтение.

На UNIX-системах (Linux и macOS) эти механизмы устроены достаточно похоже, и для их использования уже существовала библиотека libev. Но IOCP в Windows работает по совсем другой модели. Библиотека libev не поддерживала IOCP, а значит, не подходила для полноценной поддержки Windows.

Поскольку изначально ставилась цель сделать Node.js кроссплатформенным, команда приняла решение создать новую библиотеку — libuv. Она стала универсальным слоем, который умеет использовать epoll на Linux, kqueue на macOS, IOCP на Windows и при этом абстрагирует различия между платформами.

Интересный факт: изначально название “libuv” ничего не означало — это просто было удобное короткое имя, похожее на “libev”. Но поскольку люди продолжали спрашивать про значение, команда решила придумать расшифровку. Так появился Unicorn Velociraptor (то самое “uv” в названии), который стал неофициальным талисманом библиотеки.

Что внутри

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

Во-первых, внутри libuv есть различные handle-ы — структуры, представляющие абстракции над реальными ресурсами. Например, uv_tcp_t и uv_udp_t — для работы с TCP- и UDP-соединениями, uv_fs_event_t и uv_fs_poll_t — для отслеживания изменений в файловой системе. Все они наследуются от базового uv_handle_t и работают в связке с Event Loop. Поточные соединения (TCP, pipe) дополнительно реализуют интерфейс uv_stream_t.

Есть и вспомогательные handle-ы, которые управляют логикой Event Loop на разных фазах. uv_timer_t запускает колбэк через заданное время, uv_idle_t позволяет выполнять действия, когда Event Loop «пустует», а uv_prepare_t и uv_check_t — вставляют логику до и после опроса событий соответственно.

Для операций, которые невозможно выполнить асинхронно на уровне ОС — таких как доступ к файловой системе или DNS — libuv использует встроенный пул потоков. Это набор фоновых воркеров, куда можно отправить синхронную задачу, не блокируя основной поток. Через него реализованы такие функции, как uv_fs_* (работа с файлами), uv_getaddrinfo (разрешение DNS), а также пользовательские задачи через uv_queue_work.

Наконец, libuv содержит набор вспомогательных утилит: функции работы с переменными окружения, высокоточные таймеры (uv_hrtime), сбор метрик по Event Loop — и многое другое.

Таким образом, libuv — это не просто реализация Event Loop, а кроссплатформенная основа для асинхронного программирования, которая закрывает почти все аспекты взаимодействия с операционной системой.


Следующие части

Погружение в libuv. Часть 2. Неблокирующий ввод-вывод.

Погружение в libuv. Часть 3. Опять Event Loop.

Погружение в libuv. Часть 4. Другие функции.

Report Page