Почему цикл while блокирует цикл событий?

Следующий пример приведен в книге Node.js:

var open = false; setTimeout(function() { open = true }, 1000) while (!open) { console.log('wait'); } console.log('open sesame'); 

Объясняя, почему цикл while блокирует выполнение, автор говорит:

Узел никогда не выполнит обратный вызов тайм-аута, потому что цикл события застрял на этом, а цикл запущен в строке 7, никогда не давая ему возможности обработать событие тайм-аута!

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

Может ли кто-то уточнить это? Почему узел застревает? И как изменить код выше, сохраняя при while структуру управления, чтобы цикл события не блокировался, и код будет вести себя так, как можно было бы ожидать. wait будет занесен в журнал только за 1 секунду до того, как будет setTimeout и затем процесс завершится после регистрации «открытого кунжута».

Общие объяснения, такие как ответы на этот вопрос об IO и циклах событий и обратных вызовах, действительно не помогают мне рационализировать это. Я надеюсь, что ответ, который непосредственно ссылается на приведенный выше код, поможет.

На самом деле это довольно просто. Внутренне node.js состоит из этого типа цикла:

  • Получить что-то из очереди событий
  • Запустите любую задачу и запустите ее, пока она не вернется
  • Когда вышеуказанная задача будет выполнена, получите следующий элемент из очереди событий
  • Запустите любую задачу и запустите ее, пока она не вернется
  • Полоскание, пена, повторение – снова и снова

Если в какой-то момент в очереди событий ничего нет, перейдите в режим сна, пока в очереди событий не появится что-то.


Итак, если часть Javascript сидит в то while() , то эта задача не заканчивается и в приведенной выше последовательности ничего нового не будет выбрано из очереди событий до тех пор, пока эта предварительная задача не будет полностью выполнена. Таким образом, очень длинный или вечно бегущий while() цикл просто подтягивает работы. Поскольку Javascript запускает только одну задачу за один раз (однопоточный для выполнения JS), если эта одна задача вращается в цикле while, тогда ничего не может быть выполнено.

Вот простой пример, который мог бы объяснить это:

  var done = false; // set a timer for 1 second from now to set done to true setTimeout(function() { done = true; }, 1000); // spin wait for the done value to change while (!done) { /* do nothing */} console.log("finally, the done value changed!"); 

Некоторые могут логически полагать, что цикл while будет вращаться, пока не заработает таймер, а затем таймер изменит значение done на true а затем цикл while завершится, и console.log() в конце будет выполнена. Это НЕ, что произойдет. На самом деле это будет бесконечный цикл, и оператор console.log() никогда не будет выполнен.

Проблема в том, что, как только вы войдете в ожидание вращения в цикле while() , НИКАКИЙ другой Javascript не сможет выполнить. Таким образом, таймер, который хочет изменить значение done переменной, не может выполнить. Таким образом, условие цикла while никогда не может измениться и, следовательно, это бесконечный цикл.

Вот что происходит внутри механизма JS:

  1. done переменная, инициализированная значением false
  2. setTimeout() запускает событие таймера в течение 1 секунды
  3. Цикл while начинает вращаться
  4. 1 секунда во вращающемся цикле while, таймер срабатывает внутри двигателя JS, и обратный вызов таймера добавляется в очередь событий. Вероятно, это происходит в другом streamе, внутреннем для JS-движка.
  5. Цикл while продолжает вращаться, потому что переменная done никогда не изменяется. Поскольку он продолжает вращаться, JS-движок никогда не заканчивает этот stream выполнения и никогда не сможет вытащить следующий элемент из очереди событий.

Узел представляет собой единую последовательную задачу. Нет параллелизма, и его параллелизм связан с IO. Подумайте об этом так: все работает на одном streamе, когда вы делаете вызов IO, который блокирует / синхронно, ваш процесс останавливается до тех пор, пока данные не будут возвращены; однако говорят, что у нас есть единственный stream, который вместо ожидания на IO (чтение диска, захват URL-адреса и т. д.) ваша задача переходит к следующей задаче, и после завершения этой задачи она проверяет, что IO. Это в основном то, что делает узел, его «цикл событий» – его опрос IO для завершения (или прогресса) в цикле. Поэтому, когда задача не завершается (ваш цикл), цикл событий не прогрессирует. Проще говоря.

Это отличный вопрос, но я нашел исправление!

 var sleep = require('system-sleep') var done = false setTimeout(function() { done = true }, 1000) while (!done) { sleep(100) console.log('sleeping') } console.log('finally, the done value changed!') 

Я думаю, что это работает, потому что system-sleep – это не ожидание вращения.

потому что таймер должен вернуться и ждать цикла, чтобы закончить, чтобы добавить в очередь, поэтому, хотя тайм-аут находится в отдельном streamе и может действительно завершить таймер, но «задача» для установки done = true ожидает этого бесконечного цикла заканчивать

  • npm ERR! Ошибка: подключите ECONNREFUSED
  • Как вы используете Mongoose без определения схемы?
  • nodejs connect не может найти статический
  • Что произойдет, если я отклоняю / разрешаю несколько раз в цикле Крисковаля?
  • Как установить NODE_ENV для производства / разработки в OS X
  • Есть ли способ скомпилировать исходные файлы node.js?
  • Как определить IP-адрес пользователя в узле
  • Mongoose findByIdAndUpdate не возвращает правильную модель
  • RabbitMQ / AMQP: одиночная очередь, несколько потребителей для одного сообщения?
  • Как отлаживать «Ошибка: вызвать ENOENT» на node.js?
  • nodejs - http.createServer, похоже, дважды звонит
  • Interesting Posts
    Давайте будем гением компьютера.