Почему функция и обратный вызов не блокируются в Node.JS? - PullRequest
10 голосов
/ 20 февраля 2012

Новичок понимает Node: если я перезаписываю синхронный или встроенный код для использования функций / обратных вызовов, я могу гарантировать, что мой код не блокируется. Мне любопытно, как это работает с точки зрения стека событий. Вот простой пример: Не понимаю, обратный вызов - Stackoverflow состоит в том, что это будет блокировать:

var post = db.query("select * from posts where id = 1");
doSomethingWithPost(post)
doSomethingElse();

Пока не получится:

callback = function(post){
doSomethingWithPost(post)
}

db.query("select * from posts where id = 1",callback);
doSomethingElse();

Хорошо, я понимаю, что мы должны использовать обратные вызовы. Но с точки зрения стека событий, почему это работает? Javascript является однопоточным. В первой строке примера используется дорогостоящая и блокирующая операция ввода-вывода. Строка 2 не может быть выполнена, пока не закончится первая строка. Это потому, что для строки 2 требуется информация из строки 1? Или это потому, что события ввода / вывода просто блокируют операции, а это означает, что они захватывают контроль и не возвращают его до завершения ...

Во втором примере дорогой ввод / вывод был перемещен в функцию, и теперь у нас есть функция обратного вызова. Конечно, обратный вызов не может быть выполнен до тех пор, пока не будет выполнен ввод-вывод. Это не изменится. Таким образом, разница в количестве времени, которое требуется для выполнения между одним и двумя, должна в основном заключаться в том, что произойдет, если второй запрос попадет на сервер.

Если второй запрос попадет в пример один, он не сможет обработать, пока запрос 1 не будет выполнен из-за операции блокировки ... но в примере два ... перемещение операций в функции автоматически порождает дочерние процессы или действует как многопоточный? Если бы Javscript был однопоточным, это все равно представляло бы проблему, если бы не было какого-либо способа параллельной обработки. Гарантирует ли функция / обратный вызов только то, что она неблокирующая, ЕСЛИ мы используем неблокирующие методы, такие как дочерние процессы и т. Д. .

Ответы [ 4 ]

19 голосов
/ 20 февраля 2012

Представьте, что вы управляете кассовым аппаратом в пекарне.Вы работаете со своими клиентами последовательно и синхронно, например:

  1. Примите заказ
  2. Скажите пекарю выпекать хлеб
  3. Подождите, пока хлеб не испечется
  4. Взимать деньги
  5. Доставить хлеб
  6. GOTO 1 - следующий клиент

Это будет очень медленно.Теперь попробуйте вместо этого принимать заказы последовательно, но обрабатывайте своих клиентов асинхронно:

  1. Примите заказ
  2. Скажите пекарю испечь хлеб и уведомить вас о завершении.При получении уведомления:
    1. Платные деньги
    2. Доставка хлеба
  3. GOTO 1 - следующий клиент

ОБНОВЛЕНИЕ: Я рефакторинг выше, так что это больше похоже на обратный вызов.Вы, кассир, сразу перейдете к шагу 3 после того, как отдадите заказ пекарю.Вы перейдете к шагу 2.1, когда пекарь уведомит вас о том, что хлеб готов.

Таким образом, вы все равно будете доставлять столько хлеба - вы можете продать столько хлеба, сколько выпекает ваш пекарь.Но вы можете работать с вашими клиентами более эффективно, потому что вместо ожидания ожидания возврата заказа вы начинаете обрабатывать следующего клиента.

Теперь вы можете заняться всеми видами фантазий,и начислять деньги заранее, и сказать клиенту, чтобы он взял хлеб на другом конце стола, или что-то в этом роде.Я думаю, что Starbucks довольно "выровнены" в этом смысле.Кассир принимает заказ, выдает несколько запросов на вещи и говорит клиенту подождать, пока все не окажется в зоне вывоза.Супер-эффективный.

Теперь представьте, что ваш друг начинает работать с другим кассовым аппаратом.Он следует вашему асинхронному примеру.Вы можете справиться с большим количеством клиентов, даже быстрее!Обратите внимание, что единственное, что вам нужно было сделать, - это отправить своего друга туда и передать ему ваш рабочий процесс.

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

Итак, нет, «перемещение операций в функции» не «автоматически порождает дочерние процессы».Они больше похожи на сигналы тревоги - когда это закончено, уведомите меня и позвольте мне поднять на этом этапе, "этот момент" является кодом в вашем обратном вызове.Но обратный вызов будет по-прежнему выполняться в том же процессе и том же потоке.

Теперь, node.js также управляет внутренним пулом потоков для ввода-вывода.Это отвлечено от вас: чтобы продолжить аналогию с пекарней, допустим, у вас есть «пул пекарей» - вам, стоя у кассы, вам не нужно знать об этом.Вы просто даете им заказ («одна буханка закваски») и доставляете этот заказ, когда получаете уведомление о его завершении.Но пекари пекут свой хлеб параллельно, в своем собственном «пекарском пуле».

3 голосов
/ 20 февраля 2012

Я плохо разбираюсь в английском, поэтому не могу понять, что вы имеете в виду.Но я могу сказать, что «многопоточный» и «асинхронный» - это схожие, но разные термины.Даже если один поток может выглядеть как «асинхронный».

Этот документ не предназначен для узла (он предназначен для асинхронной среды Python, «искривленной»), но может быть полезен для вас.

извините за мой плохой английский.

0 голосов
/ 12 июля 2018

ВАЖНО - db.query () не блокирует стек, поскольку он выполняется в другом месте.

Блоки примера 1, потому что в строке 2 требуется информация из строки 1, и, следовательно, она должна дождаться завершения строки 1.Строка 3 не может быть выполнена перед строкой 2, так как код обрабатывается в порядке строки, и поэтому строка 2 блокирует строку 3 и любые другие строки.

Пример 2 неблокирует, так как функция обратного вызова не вызывается длявыполняется до завершения db.query, поэтому он не блокирует выполнение doSomethingElse ().

0 голосов
/ 19 апреля 2018

Принятый ответ - это здорово, но я бы хотел добавить что-то чисто связанное с nonblocking, а именно эту часть вопроса:

Или это потому, что события ввода / вывода просто принципиально блокируют операции, означающие, что они захватывают контроль и не возвращают его пока не сделано ...

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

Как правило, это сводится к системному вызову, подобному POSIX select (или версия Microsoft того же ), или к более поздним вариантам той же идеи, что и Linux . epoll.

Гипотетически говоря, если мы увидим функцию, подобную db.query в вашем примере, и предположим, что мы также знаем, что инфраструктура, обеспечивающая эту функцию, не полагается на какую-либо многопоточность, тогда она обычно безопасна сделать вывод, что:

  • Каркас отслеживает глобальный список дескрипторов ввода-вывода и обратных вызовов, связанных с любыми неблокирующими запросами ввода-вывода, которые были инициированы, например ваш db.query вызов.
  • Фреймворк имеет или опирается на какой-то основной цикл событий приложения. Это может быть что угодно от старой школы while(true) до чего-то вроде libev
  • Где-то в указанном главном цикле, select или аналогичная функция будет использоваться для проверки, завершился ли еще какой-либо из ожидающих запросов ввода-вывода.
  • Когда найден законченный запрос ввода-вывода, вызывается связанный с ним обратный вызов, после чего возобновляется основной цикл.

При вызове SQL DB, таком как ваш db.query, вероятно, используется сетевой сокет IO , а не файловый IO , но с вашей точки зрения, как разработчика приложения, дескрипторы сокетов и файлов обрабатываются в почти идентичные пути во многих операционных системах, и в любом случае они могут быть переданы в select на POSIX-подобных.

Обычно однопоточные однопроцессные серверные приложения "жонглируют" несколькими одновременными соединениями.

...