Когда используется пул потоков?

Итак, у меня есть понимание того, как работает Node.js: у него есть один поток прослушивателя, который получает событие, а затем делегирует его в рабочий пул. Рабочий поток уведомляет слушателя о завершении работы, а затем слушатель возвращает ответ вызывающему.

Мой вопрос заключается в следующем: если я встану на HTTP-сервер в Node.js и вызову сон на одном из моих событий маршрутизируемого пути (например, "/test/sleep" ), вся система остановится. Даже поток одного слушателя. Но я понял, что этот код происходит в рабочем пуле.

Теперь, напротив, когда я использую Mongoose для общения с MongoDB, чтение БД является дорогостоящей операцией ввода-вывода. Node, кажется, способен делегировать работу потоку и получать обратный вызов, когда он завершается; время, затраченное на загрузку из БД, похоже, не блокирует систему.

Как Node.js решает использовать поток пула потоков и поток слушателя? Почему я не могу написать код события, который спит и только блокирует поток пула потоков?

Ответ 1

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

На данный момент мы будем игнорировать явную многопроцессорную/многопоточную обработку через cluster и webworker-threads, и просто поговорим о типичном не-потоковом node.

Node работает в одном цикле событий. Это однопоточное, и вы получаете только один поток. Все javascript, который вы пишете, выполняются в этом цикле, и если в этом цикле происходит операция блокировки, тогда он блокирует весь цикл, и ничего не произойдет до тех пор, пока он не завершится. Это типично однопоточный характер node, о котором вы так много слышите. Но это не вся картина.

Некоторые функции и модули, обычно написанные на C/С++, поддерживают асинхронный ввод-вывод. Когда вы вызываете эти функции и методы, они внутренне управляют передачей вызова в рабочий поток. Например, когда вы используете модуль fs для запроса файла, модуль fs передает этот вызов рабочему потоку, и этот рабочий ждет ответа, который затем возвращается в цикл событий, который был показывая это без него. Все это отвлечено от вас, разработчика node, и некоторые из них отвлечены от разработчиков модулей с помощью libuv.

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


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

  • Любой внешний модуль, который вы включаете в свой проект, который использует собственные С++ и libuv, скорее всего, будет использовать пул потоков (думаю: доступ к базе данных)
  • libuv имеет размер пула потоков по умолчанию, равный 4, и использует очередь для управления доступом к пулу потоков. Результатом является то, что если у вас есть 5 длительных запросов БД, все они идут одновременно, один из них (и любое другое асинхронное действие, основанное на пуле потоков) будет ждать завершения этих запросов до их начала.
  • Вы можете уменьшить это, увеличив размер пула потоков через переменную среды UV_THREADPOOL_SIZE, пока вы это делаете до того, как пул потоков будет создан и создан: process.env.UV_THREADPOOL_SIZE = 10;

Если вы хотите традиционную многопроцессорную обработку или многопоточность в node, вы можете получить ее через встроенный модуль cluster или другие другие модули, такие как вышеупомянутый webworker-threads, или вы можете подделать его, выполнив каким-то образом разбить вашу работу и вручную с помощью setTimeout или setImmediate или process.nextTick, чтобы приостановить вашу работу и продолжить ее в более позднем цикле, чтобы другие процессы завершились (но это не рекомендуется).

Обратите внимание: если вы пишете длинный пробег/код блокировки в javascript, вы, вероятно, ошибаетесь. Другие языки будут работать гораздо эффективнее.

Ответ 2

Итак, у меня есть понимание того, как работает Node.js: у него есть один поток прослушивателя, который получает событие, а затем делегирует его в рабочий пул. Рабочий поток уведомляет слушателя о завершении работы, а затем слушатель возвращает ответ вызывающему.

Это не очень точно. Node.js имеет только один "рабочий" поток, выполняющий javascript. Внутри node есть потоки, обрабатывающие обработку ввода-вывода, но думать о них как о "рабочих" - заблуждение. На самом деле есть только обработка ввода-вывода и несколько других деталей внутренней реализации node, но как программист вы не можете влиять на их поведение, кроме нескольких нечетных параметров, таких как MAX_LISTENERS.

Мой вопрос заключается в следующем: если я встану на HTTP-сервер в Node.js и вызову сон на одном из моих событий маршрутизируемого пути (например, "/test/sleep" ), вся система остановится. Даже поток одного слушателя. Но я понял, что этот код происходит в рабочем пуле.

В JavaScript нет механизма сна. Мы могли бы обсудить это более конкретно, если бы вы опубликовали фрагмент кода того, что вы считаете "спящим". Нет такой функции, чтобы вызвать симуляцию, например, time.sleep(30) в python. Там setTimeout но это принципиально НЕ спят. setTimeout и setInterval явно release, а не block, цикл событий, так что другие биты кода могут выполняться в основном потоке выполнения. Единственное, что вы можете сделать, это заняться циклом CPU с вычислением в памяти, который действительно будет голодать основной поток выполнения и отринуть вашу программу без ответа.

Как Node.js решает использовать поток пула потоков и поток слушателя? Почему я не могу написать код события, который спит и только блокирует поток пула потоков?

Сеть IO всегда асинхронна. Конец истории. Disk IO имеет как синхронные, так и асинхронные API, поэтому нет "решения". Node.js будет вести себя в соответствии с основными функциями API, которые вы вызываете sync vs normal async. Например: fs.readFile vs fs.readFileSync. Для дочерних процессов существуют также отдельные API child_process.exec и child_process.execSync.

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

Ответ 3

Это недоразумение - это просто различие между упреждающей многозадачностью и совместной многозадачностью...

Сон выключает весь карнавал, потому что на всех рейдах действительно одна линия, и вы закрыли ворота. Подумайте об этом как "интерпретатор JS и некоторые другие вещи" и проигнорируйте потоки... для вас есть только один поток,...

... поэтому не блокируйте его.