Как однопоточная неблокирующая IO-модель работает в Node.js

Я не программист Node, но меня интересует, как работает однопоточная неблокирующая модель ввода-вывода. После того, как я прочитал статью understanding-the-node-js-event-loop, я действительно смущен. Он привел пример модели:

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);

Вот вопрос. Когда есть два запроса A (на первом месте) и B, поскольку существует только один поток, программа на стороне сервера будет обрабатывать запрос A во-первых: выполнение sql-запроса, которое является оператором сна, стоящим для ожидания ввода-вывода. И программа застревает в ожидании ввода-вывода и не может выполнить код, который отображает веб-страницу. Будет ли программа переключаться на запрос B во время ожидания? На мой взгляд, из-за модели с одним потоком нет возможности переключить один запрос от другого. Но заголовок кода примера говорит, что "все работает параллельно, кроме вашего кода". (P.S Я не уверен, если я неправильно понял код или нет, так как я никогда не использовал Node.) Как Node переключить A в B во время ожидания? И вы можете просто объяснить однопоточную неблокирующую модель ввода-вывода Node? Буду признателен, если вы поможете мне.:)

Ответ 1

Node.js построен на libuv, кросс-платформенной библиотеке, которая абстрагирует apis/syscalls для асинхронных (неблокирующих ) ввод/вывод, предоставляемый поддерживаемыми ОС (по крайней мере, Unix, OS X и Windows).

Асинхронный IO

В этой модели программирования операция открытия/чтения/записи на устройствах и ресурсах (сокеты, файловая система и т.д.), управляемые файловой системой, не блокирует вызывающий поток (как в типичной синхронной c-образной модели), так и просто отметьте процесс (в структуре данных уровня ядра/ОС), чтобы получать уведомления о появлении новых данных или событий. В случае приложения с веб-сервером процесс затем отвечает, чтобы выяснить, какой запрос/контекст принадлежит уведомленному событию и продолжить обработку запроса оттуда. Обратите внимание, что это обязательно означает, что вы будете находиться в другом стеке стека из того, который инициировал запрос к ОС, поскольку последний должен был уступить диспетчеру процесса, чтобы один поток обрабатывал новые события.

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

Node (продолжение стиля перехода и цикл событий)

Node решает проблему с использованием функций языка javascript, чтобы сделать эту модель немного более синхронной, побуждая программиста использовать определенный стиль программирования. Каждая функция, запрашивающая IO, имеет подпись типа function (... parameters ..., callback) и ей необходимо получить обратный вызов, который будет вызываться, когда запрошенная операция будет завершена (помните, что большая часть времени тратится, ожидая, пока ОС сообщит о завершении - время которые могут быть потрачены на выполнение другой работы). Поддержка Javascript для закрытия позволяет вам использовать переменные, которые вы определили во внешней (вызывающей) функции внутри тела обратного вызова - это позволяет сохранить состояние между различными функциями, которые будут выполняться независимо от времени выполнения node. См. Также Продолжение стиля перехода.

Кроме того, после вызова функции, создающей операцию ввода-вывода, вызывающая функция обычно return управляет циклом событий node . Этот цикл вызовет следующий обратный вызов или функцию, которая была запланирована для выполнения (скорее всего, потому что соответствующее событие было уведомлено ОС) - это позволяет выполнять одновременную обработку нескольких запросов.

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

Высококонкурентный, без parallelism

В качестве заключительного замечания фраза "все работает параллельно, кроме вашего кода" делает достойную работу по захвату точки, в которой node позволяет вашему коду обрабатывать запросы из сотен тысяч открытых сокетов с одним потоком одновременно путем мультиплексирования и упорядочивая всю вашу js-логику в одном потоке исполнения (хотя высказывание "все работает параллельно", вероятно, неверно здесь - см. Concurrency vs Parallelism - Что такое разница?). Это очень хорошо работает для серверов webapp, поскольку большая часть времени фактически тратится на ожидание сети или диска (базы данных/сокетов), а логика не очень интенсивно для процессора - то есть: это хорошо работает для IO-bound Рабочие нагрузки.

Ответ 2

Хорошо, чтобы дать некоторую перспективу, позвольте мне сравнить node.js с apache.

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

Node.js, с другой стороны, управляется событиями, обрабатывая все запросы асинхронно из одного потока.

Когда A и B получены в apache, создаются два потока, которые обрабатывают запросы. Каждый обрабатывает запрос отдельно, каждый из которых ожидает результатов запроса перед обслуживанием страницы. Страница выполняется только до тех пор, пока запрос не будет завершен. Извлечение запроса блокируется, потому что сервер не может выполнить остальную часть потока, пока не получит результат.

В node c.query обрабатывается асинхронно, что означает, что в то время как c.query извлекает результаты для A, он переходит к обработке c.query для B, а когда результаты приходят для A, он отправляет результаты для обратного вызова, который отправляет ответ. node.js знает, как выполнить обратный вызов при завершении выборки.

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

На самом деле сервер node делает именно это для вас все время. Для создания переключателей (асинхронное поведение) большинство функций, которые будут использоваться, будут иметь обратные вызовы.

Изменить

SQL-запрос берется из библиотеки mysql. Он реализует стиль обратного вызова, а также эмитент событий для запросов SQL в очереди. Он не выполняет их асинхронно, что выполняется внутренними потоками libuv, которые обеспечивают абстракцию неблокирующего ввода-вывода. Для выполнения запроса выполняются следующие шаги:

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

Входящие запросы на http-сервер обрабатываются аналогичным образом. Архитектура внутреннего потока выглядит примерно так:

node.js event loop

Потоки С++ - это libuv, которые выполняют асинхронный ввод-вывод (диск или сеть). Цикл основного события продолжает выполняться после отправки запроса в пул потоков. Он может принимать больше запросов, так как он не ждет и не спит. SQL-запросы/HTTP-запросы/файловая система читают все, что происходит.

Ответ 3

Node.js использует libuv за кулисами. libuv имеет пул потоков (по умолчанию размер 4). Поэтому Node.js использует потоки для достижения concurrency.

Однако, ваш код запускается в одном потоке (т.е. все обратные вызовы функций Node.js будут вызываться в одном потоке, так называемый цикл-поток или событие- петля). Когда люди говорят, что "Node.js работает в одном потоке", они действительно говорят "обратные вызовы Node.js работают в одном потоке".

Ответ 4

Node.js основан на модели программирования цикла событий. Цикл событий работает в одном потоке и многократно ждет событий, а затем запускает любые обработчики событий, подписанные на эти события. События могут быть, например,

  • Ожидание таймера завершено
  • следующий фрагмент данных готов к записи в этот файл
  • theres новый новый запрос HTTP, пришедший на наш путь

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

(Там немного магии под капотом, где происходят события. Некоторые из них связаны с рабочими потоками низкого уровня, которые работают параллельно).

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

event loop high level view

В соответствии с: "Цикл событий из концепции 10 000 футов позади Node.js" .

Ответ 5

Функция c.query() имеет два аргумента

c.query("Fetch Data", "Post-Processing of Data")

В этом случае операция "Извлечь данные" представляет собой DB-Query, теперь это может обрабатываться Node.js путем нереста рабочего потока и предоставления ему этой задачи выполнения DB-Query. (Помните Node.js может создавать поток внутри). Это позволяет функции мгновенно возвращаться без задержки

Второй аргумент "Постобработка данных" - это функция обратного вызова, структура node регистрирует этот обратный вызов и вызывается циклом события.

Таким образом, оператор c.query (paramenter1, parameter2) будет возвращаться мгновенно, позволяя node обслуживать другой запрос.

PS: Я только начал понимать node, на самом деле я хотел написать это как комментарий к @Philip, но так как у него не было достаточно очков репутации, поэтому написал это как ответ.

Ответ 6

если вы прочтете еще немного: "Конечно, на бэкэнд есть потоки и процессы для доступа к БД и выполнения процесса. Однако они явно не подвержены вашему коду, поэтому вы не можете беспокоиться о них, кроме как зная, что взаимодействия ввода-вывода, например, с базой данных или с другими процессами, будут асинхронными с точки зрения каждого запроса, поскольку результаты этих потоков возвращаются через цикл событий в ваш код."

about - "все работает параллельно, кроме вашего кода" - ваш код выполняется синхронно, всякий раз, когда вы вызываете асинхронную операцию, такую ​​как ожидание ввода-вывода, цикл событий обрабатывает все и вызывает обратный вызов. это просто не то, о чем вы должны думать.

в вашем примере: есть два запроса A (на первом месте) и B. вы выполняете запрос A, ваш код продолжает работать синхронно и выполнять запрос B. цикл события обрабатывает запрос A, когда он завершает его, вызывает обратный вызов запрос A с результатом, то же самое относится к запросу B.

Ответ 7

Хорошо, большинство вещей должно быть ясным до сих пор... сложная часть - это SQL: если она не работает в другом потоке или процессе в своем полнота, SQL-исполнение должно быть разбито на отдельные шаги (обработчиком SQL, выполненным для асинхронного выполнения!), где выполняются неблокирующие, и блокирующие (например, сон ) на самом деле могут быть переданы ядру (как прерывание/событие тревоги) и помещены в список событий для основного цикла.

Это означает, например, интерпретация SQL и т.д. выполняется немедленно, но во время ожидания (хранится как событие, которое должно появиться в будущем ядром в некоторой структуре kqueue, epoll,... вместе с другими операциями ввода-вывода) основной цикл может делать другие вещи и, в конечном счете, проверять, произошло ли что-то из этих МО и ждет.

Итак, чтобы перефразировать его снова: программа никогда не (заводится) застревает, сонные вызовы никогда не выполняются. Их долг выполняется ядром (пишите что-нибудь, ждите, что что-то придет через сеть, ожидая времени, чтобы пройти) или другого потока или процесса. - Процесс Node проверяет, завершено ли по крайней мере одна из этих обязанностей ядром в единственном блокирующем вызове ОС один раз в каждом цикле цикла событий. Этот момент достигается, когда выполняется все неблокирование.

Clear?: -)

Я не знаю Node. Но откуда берется c.query?