У Node совершенно другая парадигма, и как только она будет правильно схвачена, будет легче увидеть этот другой способ решения проблем. Вам никогда не понадобится несколько потоков в приложении Node (1), потому что у вас есть другой способ сделать то же самое. Вы создаете несколько процессов; но это очень сильно отличается от, например, того, как это делает, например, Prefork mpm веб-сервера Apache.
А пока давайте подумаем, что у нас есть только одно ядро процессора, и мы разработаем приложение (в стиле Node) для выполнения некоторой работы. Наша задача - побайтово обрабатывать большой файл с его содержимым. Лучший способ для нашей программы - начать работу с начала файла, побайтно до конца.
- Привет, Хасан, я полагаю, ты либо новичок, либо очень олдскульный со времен моего дедушки !!! Почему бы вам не создать несколько потоков и не сделать их намного быстрее?
- Ой, у нас только одно ядро процессора.
-- Ну и что? Создайте несколько потоков, чувак, сделайте это быстрее!
- Так не работает. Если я создам потоки, я буду делать это медленнее. Потому что я буду добавлять в систему много накладных расходов на переключение между потоками, пытаясь дать им определенное количество времени, а внутри моего процесса - пытаться общаться между этими потоками. В дополнение ко всем этим фактам, мне также придется подумать о том, как я разделю одну работу на несколько частей, которые можно будет выполнять параллельно.
- Хорошо, хорошо, я вижу, ты бедный. Воспользуемся моим компьютером, у него 32 ядра!
- Ух ты, мой дорогой друг, большое тебе спасибо. Я ценю его!
Затем вернемся к работе. Теперь у нас 32 ядра процессора благодаря нашему богатому другу. Правила, которые мы должны соблюдать, только что изменились. Теперь мы хотим использовать все это богатство, которое нам дано.
Чтобы использовать несколько ядер, нам нужно найти способ разделить нашу работу на части, которые мы можем обрабатывать параллельно. Если бы это был не Node, мы бы использовали для этого потоки; 32 потока, по одному на каждое ядро процессора. Однако, поскольку у нас есть Node, мы создадим 32 процесса Node.
Потоки могут быть хорошей альтернативой процессам Node, возможно, даже лучшим способом; но только в конкретном виде работы, где работа уже определена, и мы полностью контролируем, как с ней справиться. Кроме этого, для любого другого рода проблемы, когда работа идет снаружи так, как мы не имеем контроля над и мы хотим, чтобы ответить как можно быстрее, путем Node является неоспоримо выше.
- Привет, Хасан, ты еще работаешь однопоточным? Что с тобой не так, мужик? Я только что предоставил вам то, что вы хотели. Тебе больше нет оправданий. Создавайте потоки, заставляйте их работать быстрее.
- Я разделил работу на части, и каждый процесс будет работать над одной из этих частей параллельно.
- Почему вы не создаете темы?
- Извините, я не думаю, что это можно использовать. Вы можете взять свой компьютер, если хотите?
- Нет ладно, я крут, просто не понимаю, почему ты не используешь нитки?
- Спасибо за компьютер. :) Я уже разделил работу на части и создаю процессы для параллельной работы над этими частями. Все ядра процессора будут полностью загружены. Я мог бы сделать это с помощью потоков, а не процессов; но у Node есть такой способ, и мой босс Парт Таккар хочет, чтобы я использовал Node.
- Хорошо, дайте мне знать, если вам понадобится другой компьютер. :п
Если я создам 33 процесса вместо 32, планировщик операционной системы будет приостанавливать поток, запускать другой, приостанавливать его после нескольких циклов, запускать другой снова ... Это ненужные накладные расходы. Я этого не хочу. Фактически, в системе с 32 ядрами я бы даже не хотел создавать ровно 32 процесса, 31 может быть лучше . Потому что в этой системе будет работать не только мое приложение. Было бы неплохо оставить немного места для других вещей, особенно если у нас 32 комнаты.
Я считаю, что сейчас мы находимся на одной странице в отношении полного использования процессоров для задач, интенсивно использующих процессор .
- Хм, Хасан, извини, что немного поиздевался над тобой. Думаю, теперь я понимаю вас лучше. Но есть еще кое-что, что мне нужно объяснить: что за шум о запуске сотен потоков? Я везде читал, что потоки создаются намного быстрее и тупее, чем процессы разветвления? Вы разветвляете процессы, а не потоки, и думаете, что это самый высокий уровень, который вы можете получить с помощью Node. Тогда разве Node не подходит для такой работы?
- Не волнуйся, я тоже крут. Все говорят такие вещи, так что я привык их слышать.
-- Так? Узел не подходит для этого?
- Node отлично подходит для этого, хотя потоки тоже могут быть хорошими. Что касается накладных расходов на создание потока / процесса; на том, что вы часто повторяете, на счету каждая миллисекунда. Однако я создаю всего 32 процесса, и это займет совсем немного времени. Произойдет это только один раз. Это не будет иметь никакого значения.
- Когда тогда я хочу создать тысячи потоков?
- Вы никогда не захотите создавать тысячи потоков. Однако в системе, которая выполняет работу извне, например, веб-сервер, обрабатывающий HTTP-запросы; если вы используете поток для каждого запроса, вы будете создавать много потоков, многие из них.
- Но ведь узел другой? Правильно?
-- Да, точно. Вот где действительно проявляется Node. Подобно тому, как поток намного легче процесса, вызов функции намного легче потока. Узел вызывает функции вместо создания потоков. В примере с веб-сервером каждый входящий запрос вызывает вызов функции.
- Хм, интересно; но вы можете запускать только одну функцию одновременно, если вы не используете несколько потоков. Как это может работать, если на веб-сервер одновременно поступает много запросов?
- Вы совершенно правы в том, что функции выполняются по одной, а не две параллельно. Я имею в виду, что в одном процессе одновременно выполняется только одна область кода. Планировщик ОС не приходит и не приостанавливает эту функцию и не переключается на другую, если только он не приостанавливает процесс, чтобы дать время другому процессу, а не другому потоку в нашем процессе. (2)
- Тогда как процесс может обрабатывать 2 запроса одновременно?
- Процесс может обрабатывать десятки тысяч запросов одновременно, если в нашей системе достаточно ресурсов (ОЗУ, Сеть и т. Д.). КЛЮЧЕВОЕ ОТЛИЧИЕ от того, как работают эти функции.
- Хм, а я должен волноваться?
- Может быть :) Узел запускает цикл по очереди. В этой очереди находятся наши задания, т. Е. Звонки, которые мы начали обрабатывать входящими запросами. Самым важным моментом здесь является то, как мы проектируем наши функции для работы. Вместо того, чтобы начинать обработку запроса и заставлять вызывающего абонента ждать, пока мы закончим работу, мы быстро завершаем нашу функцию после выполнения приемлемого объема работы. Когда мы подходим к точке, где нам нужно дождаться, пока другой компонент выполнит некоторую работу и вернет нам значение, вместо того, чтобы ждать этого, мы просто завершаем нашу функцию, добавляя остальную работу в очередь.
- Это звучит слишком сложно?
- Нет-нет, это может показаться сложным; но сама система очень проста и имеет смысл.
Теперь я хочу прекратить цитировать диалог между этими двумя разработчиками и закончить свой ответ, приведя последний быстрый пример того, как работают эти функции.
Таким образом, мы делаем то, что обычно делает планировщик ОС. В какой-то момент мы приостанавливаем нашу работу и позволяем другим вызовам функций (например, другим потокам в многопоточной среде) выполняться, пока снова не дойдем до нашей очереди. Это намного лучше, чем оставлять работу планировщику ОС, который пытается уделить время каждому потоку в системе. Мы знаем, что делаем, намного лучше, чем OS Scheduler, и ожидается, что мы остановимся, когда нам следует остановиться.
Ниже приведен простой пример, в котором мы открываем файл и читаем его, чтобы поработать с данными.
Синхронный способ:
Open File
Repeat This:
Read Some
Do the work
Асинхронный способ:
Open File and Do this when it is ready: // Our function returns
Repeat this:
Read Some and when it is ready: // Returns again
Do some work
Как видите, наша функция просит систему открыть файл и не дожидается его открытия. Он завершает себя предоставлением следующих шагов после того, как файл будет готов. Когда мы возвращаемся, Node запускает другие вызовы функций в очереди. После выполнения всех функций цикл событий переходит к следующему ходу ...
Таким образом, у Node совершенно другая парадигма, чем у многопоточной разработки; но это не значит, что в нем чего-то не хватает. Для синхронного задания (где мы можем определить порядок и способ обработки) он работает так же, как и многопоточный параллелизм. Для работы, которая приходит извне, например, запросов к серверу, она просто лучше.
(1) Если вы не создаете библиотеки на других языках, таких как C / C ++, и в этом случае вы все равно не создаете потоки для разделения заданий. Для такого рода работы у вас есть два потока, один из которых будет продолжать связь с Node, а другой будет выполнять реальную работу.
(2) Фактически, каждый процесс Node имеет несколько потоков по тем же причинам, которые я упомянул в первой сноске. Однако это не похоже на то, чтобы 1000 потоков выполняли аналогичные работы. Эти дополнительные потоки предназначены для таких вещей, как прием событий ввода-вывода и обработка межпроцессного обмена сообщениями.
ОБНОВЛЕНИЕ (как ответ на хороший вопрос в комментариях)
@Mark, спасибо за конструктивную критику. В парадигме Node у вас никогда не должно быть функций, обработка которых занимает слишком много времени, если только все остальные вызовы в очереди не предназначены для выполнения один за другим. В случае задач, требующих больших вычислительных ресурсов, если мы посмотрим на картину целиком, мы увидим, что это не вопрос «Следует ли нам использовать потоки или процессы?» но возникает вопрос: «Как мы можем сбалансированно разделить эти задачи на подзадачи, чтобы мы могли запускать их параллельно, используя несколько ядер ЦП в системе?» Допустим, мы обработаем 400 видеофайлов в системе с 8 ядрами. Если мы хотим обрабатывать один файл за раз, то нам нужна система, которая будет обрабатывать разные части одного и того же файла, и в этом случае, возможно, многопоточную однопроцессную систему будет проще построить и даже более эффективно. Мы все еще можем использовать Node для этого, запустив несколько процессов и передавая сообщения между ними, когда необходимо совместное использование состояния / связь. Как я уже сказал ранее, многопроцессорный подход с Nodeа также многопоточность в такого рода задачах; но не более того. Опять же, как я уже говорил ранее, ситуация, в которой Node сияет, - это когда у нас есть эти задачи, поступающие в систему в качестве входных данных из нескольких источников, поскольку одновременное поддержание большого количества соединений в Node намного легче по сравнению с потоком на соединение или процессом на соединение. система.
Что касается setTimeout(...,0)
звонков; иногда может потребоваться перерыв во время выполнения трудоемкой задачи, чтобы позволить вызовам в очереди иметь свою долю обработки. Разделение задач по-разному может спасти вас от этого; но, тем не менее, это не совсем взлом, это просто порядок работы очереди событий. Кроме того, использование process.nextTick
для этой цели намного лучше, поскольку при использовании setTimeout
будут необходимы подсчет и проверка прошедшего времени, в то время как process.nextTick
это просто то, что мы действительно хотим: «Эй, задача, вернись в конец очереди, вы использовали свою долю! "