Node.js построен на libuv , кроссплатформенной библиотеке, которая абстрагирует apis / syscalls для асинхронного (неблокирующего) ввода / вывода, предоставляемого поддерживаемыми ОС (по крайней мере, Unix, OS X и Windows).
Асинхронный ввод-вывод
В этой модели программирования операции открытия / чтения / записи на устройствах и ресурсах (сокеты, файловая система и т. Д.), Управляемых файловой системой , не блокируют вызывающий поток (как в типичной синхронной c-подобной модели) и просто отмечают процесс (в структуре данных уровня ядра / ОС), чтобы получать уведомления о появлении новых данных или событий. В случае приложения, подобного веб-серверу, процесс затем должен выяснить, к какому запросу / контексту относится уведомленное событие, и продолжить обработку запроса оттуда. Обратите внимание, что это обязательно будет означать, что вы будете находиться в другом стековом фрейме, чем тот, который отправил запрос к ОС, поскольку последняя должна была уступить диспетчеру процесса, чтобы однопоточный процесс обрабатывал новые события.
Проблема с моделью, которую я описал, состоит в том, что она не знакома и не вызывает трудностей у программиста, поскольку она непоследовательна по своей природе. «Вам нужно сделать запрос в функции A и обработать результат в другой функции, где ваши местные жители из A обычно недоступны».
Модель узла (Стиль прохождения продолжения и Цикл событий)
Node решает проблему, используя возможности языка javascript, чтобы сделать эту модель более синхронной, побуждая программиста использовать определенный стиль программирования. Каждая функция, которая запрашивает ввод-вывод, имеет такую же сигнатуру, function (... parameters ..., callback)и ей нужно дать обратный вызов, который будет вызван, когда запрошенная операция будет завершена (имейте в виду, что большую часть времени тратится на ожидание ОС, чтобы сообщить о завершении - время, которое может быть потратил на другую работу). Поддержка Javascript для замыканий позволяет вам использовать переменные, которые вы определили во внешней (вызывающей) функции внутри тела обратного вызова - это позволяет сохранять состояние между различными функциями, которые будут вызываться средой выполнения узла независимо. Смотрите также продолжение прохождения стиля .
Более того, после вызова функции, порождающей операцию ввода-вывода, вызывающая функция обычно returnуправляет циклом событий узла . Этот цикл вызовет следующий обратный вызов или функцию, которая была запланирована для выполнения (скорее всего, потому что соответствующее событие было уведомлено ОС) - это позволяет одновременно обрабатывать несколько запросов.
Вы можете считать цикл событий узла чем- то похожим на диспетчер ядра : ядро будет планировать выполнение заблокированного потока после завершения ожидающего ввода-вывода, в то время как узел будет планировать обратный вызов при наступлении соответствующего события.
Сильно параллельный, без параллелизма
И последнее замечание: фраза «все работает параллельно, кроме вашего кода», отлично справляется с заданием точки, позволяющей вашему коду одновременно обрабатывать запросы из сотен тысяч открытых сокетов в одном потоке путем мультиплексирования и упорядочения всех ваших js. логика в одном потоке выполнения (даже если сказать, что «все работает параллельно», вероятно, здесь не правильно - см. Параллелизм против параллелизма - в чем разница? ). Это работает очень хорошо для серверов веб-приложений, так как большую часть времени фактически тратится на ожидание сети или диска (базы данных / сокетов), и логика на самом деле не сильно загружает ЦП - то есть: это хорошо работает для рабочих нагрузок, связанных с вводом-выводом .