Параллельная модель и цикл событий.

Параллелизм в JavaScript основывается на модели "событийного цикла". Эта модель отличается от модели других языков, например C или Java.

Концепция жизненного цикла программы

В следующей секции, объясняется теоретическая модель. Современные JavaScript движки существенно оптимизируют этот процесс.

Визуальное представление

Stack, heap, queue

Стек

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

function f(b) {
  var a = 12;
  return a + b + 35;
}

function g(x) {
  var m = 4;
  return f(m * x);
}

g(21);

Когда вызывается g,  создаётся область видимости, содержащая аргументы g и локальные переменные. Когда g вызывает f, создаётся вторая область видимости и помещается в стек вперед первой, которая содержит аргументы f и её локальные переменные. Когда f возвращает результат, верхний элемент из стека удаляется. Когда g возвращает результат, ее контекст также удалится, и стек будет пуст.

Куча

Объекты размещаются в куче. Куча — это ссылка на определённую неструктурированную область памяти.

Очередь

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

Цикл событий

Модель событийного цикла (event loop) называется так, потому что отслеживает новые события в цикле:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

queue.waitForMessage ожидает события если очередь пуста.

Запуск до завершения

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

У данного подхода есть и минусы. Если событие обрабатывается слишком долго, то приложение в это время не имеет возможности обработать действия пользователя (например, скролл, или клик). Браузер может смягчать последствия такой проблемы. Internet Explorer, в таком случае, выводит сообщение "A script on this page is causing Internet Explorer to run slowly" и предлагает завершить обработку слишком тяжелого события. Хорошая практика для предохранения от этого – не создавать события, которые могут выполняться долго, и разбивать большие события на несколько мелких.

Добавление событий в очередь

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

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

Нулевые задержки

Нулевая задержка не дает гарантии, что обработчик выполнится через ноль миллисекунд. Вызов setTimeout с аргументом  0 (ноль) не завершится за указанное время. Выполнение зависит от количества ожидающих задач в очереди. Например, сообщение ''this is just a message'' из примера ниже будет выведено на консоль раньше, чем произойдет выполнение обработчика сb1. Это произойдет, потому что задержка – это минимальное время, которое требуется среде выполнения на обработку запроса.

(function () {

  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the end');

})();

// "this is the start"
// "this is just a message"
// "this is the end"
// "this is a msg from call back"
// "this is a msg from call back1"

Связь нескольких потоков между собой

Web Worker или кросс-доменный фрейм имеют свой собственный стек, кучу и очередь событий. Два отдельных событийных потока могут связываться друг с другом, только через отправку сообщений с помощью метода postMessage. Этот метод добавляет сообщение  в очередь другого, если он конечно принимает их.

Никогда не блокируется

Очень интересное свойство цикла событий в JavaScript, что в отличие от множества других языков, поток выполнения никогда не блокируется. Обработка I/O обычно осуществляется с помощью событий и функций обратного вызова, поэтому даже когда приложение ожидает запрос от IndexedDB или ответ от XHR, оно может обрабатывать другие процессы, например пользовательский ввод.

Существуют хорошо известные исключения как alert или синхронный XHR, но считается хорошей практикой избегать их использования.

Метки документа и участники

Метки: 
 Внесли вклад в эту страницу: thefasttracker, RusPosevkin, 6thSence, andrIvash, ivanlemeshev, beskrovnykh, forestbird, Saviloff, pashutk, fscholz, im7mortal
 Обновлялась последний раз: thefasttracker,