Гонки данных в JavaScript?

Предположим, я запустил этот кусок кода.

var score = 0; for (var i = 0; i < arbitrary_length; i++) { async_task(i, function() { score++; }); // increment callback function } 

В теории я понимаю, что это представляет собой гонку данных, и два потока, пытающихся увеличиваться в одно и то же время, могут привести к одному приращению, однако nodejs (и javascript) известны как однопоточные. Я уверен, что конечное значение балла будет равно произвольной длине?

Узел использует цикл событий. Вы можете думать об этом как о очереди. Поэтому мы можем предположить, что ваш цикл for ставит function() { score++; } function() { score++; } Callback arbitrary_length раз в этой очереди. После этого движок js запускает их один за другим и каждый раз увеличивает score . Так да. Единственное исключение, если обратный вызов не вызывается или доступ к переменной счетчика происходит из другого места.

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

 var results = []; for (var i = 0; i < arbitrary_length; i++) { async_task(i, function(result) { results.push(result); if (results.length == arbitrary_length) tasksDone(results); }); } 

Я уверен, что конечное значение балла будет равно произвольной длине?

Да, если все async_task() вызывают обратный вызов один раз и только один раз, вам гарантируется, что окончательное значение балла будет равно произвольной длине.

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

Существует не такая вещь, как Javascript с прерываниями (где некоторая обратная связь может прервать часть другого Javascript, который в настоящее время работает). Все сериализуется через очередь событий. Это огромное упрощение и предотвращает множество ситуаций, которые в противном случае были бы очень полезными для безопасной работы при одновременном одновременном использовании нескольких потоков или прерывания.

Все еще есть некоторые проблемы с параллелизмом, которые могут быть затронуты, но они имеют больше общего с общим состоянием, доступ к которому могут иметь множественные асинхронные обратные вызовы. Хотя только один из них когда-либо будет обращаться к нему в любой момент времени, все же возможно, что фрагмент кода, который содержит несколько асинхронных операций, может оставить какое-то состояние в состоянии «между», пока он находился в середине нескольких асинхронных операций на Где может выполняться другая операция async, и может попытаться получить доступ к этим данным.

Вы можете больше узнать о природе Javascript, связанной с событиями: как JavaScript обрабатывает ответы AJAX в фоновом режиме? И этот ответ также содержит ряд других ссылок.

И еще один подобный ответ, в котором обсуждаются условия совместного использования данных данных, которые возможны: может ли этот код вызывать состояние гонки в сокете io?

Некоторые другие ссылки:

Как я могу предотвратить обработчики событий для обработки нескольких событий сразу в javascript?

Должен ли я быть заинтересованным в условиях гонки с асинхронным Javascript?

JavaScript. Когда именно стек вызовов становится «пустым»?

Сервер Node.js с несколькими параллельными запросами, как это работает?


Чтобы дать вам представление о проблемах параллелизма, которые могут возникнуть в Javascript (даже без потоков и без прерываний, вот пример из моего собственного кода.

У меня есть сервер Raspberry Pi node.js, который контролирует поклонников чердака в моем доме. Каждые 10 секунд он проверяет два температурных датчика, один внутри чердака и один вне дома, и решает, как он должен управлять вентиляторами (через реле). Он также записывает данные о температуре, которые могут быть представлены в диаграммах. Один раз в час он сохраняет последние данные о температуре, которые были собраны в памяти, в некоторые файлы для сохранения в случае сбоя питания или сбоя сервера. Эта операция сохранения включает в себя ряд асинхронных файлов. Каждая из этих асинхронных записей возвращает управление системе, а затем продолжается, когда асинхронный обратный вызов называется завершением сигнализации. Поскольку это низкая система памяти, и данные могут потенциально занимать значительную часть доступной ОЗУ, данные не копируются в памяти перед записью (это просто непрактично). Итак, я пишу данные на диске в режиме реального времени.

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

На самом деле у меня есть оператор console.log() который явно регистрируется, когда эта проблема параллелизма возникает на моем сервере (и безопасно обрабатывается моим кодом). Это происходит раз в несколько дней на моем сервере. Я знаю, что он есть, и это реально.

Существует много способов обойти эти проблемы параллелизма. Простейшим было бы просто сделать копию в памяти всех данных, а затем выписать копию. Поскольку нет потоков или прерываний, создание копии в памяти было бы безопасно от параллелизма (не было бы уступки асинхронным операциям в середине копии для создания проблемы параллелизма). Но в этом случае это было непрактично. Итак, я выполнил очередь. Когда я начинаю писать, я устанавливаю флаг объекта, который управляет данными. Затем, в любое время, когда система хочет добавлять или изменять данные в сохраненных данных, пока этот флаг установлен, эти изменения просто переходят в очередь. Фактические данные не затрагиваются, пока этот флаг установлен. Когда данные были безопасно записаны на диск, флаг сбрасывается и обрабатываются очереди. Любую проблему параллелизма можно было избежать.


Итак, это пример проблем параллелизма, которые вам нужно беспокоиться. Одно большое упрощающее предположение с Javascript заключается в том, что часть Javascript будет завершена без какого-либо потока прерывания до тех пор, пока он не намеренно возвращает управление системе. Это делает проблемы параллелизма в обработке, как описано выше, намного проще, потому что ваш код никогда не будет прерван, кроме случаев, когда вы сознательно возвращаете систему обратно. Вот почему нам не нужны мьютексы и семафоры и другие подобные вещи в нашем собственном Javascript. Мы можем использовать простые флаги (просто регулярные Javascript-переменные), как я описал выше, если это необходимо.


В любой полностью синхронной части Javascript вы никогда не будете прерваны другим Javascript. Синхронный фрагмент Javascript будет завершен до того, как будет обработано следующее событие в очереди событий. Это означает, что Javascript является «управляемым событиями» языком. В качестве примера этого, если у вас есть этот код:

  console.log("A"); // schedule timer for 500 ms from now setTimeout(function() { console.log("B"); }, 500); console.log("C"); // spin for 1000ms var start = Date.now(); while(Data.now() - start < 1000) {} console.log("D"); 

В консоли вы получите следующее:

 A C D B 

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

Вот последовательность событий под обложками.

  1. Этот JS запускается.
  2. console.log("A") .
  3. Событие таймера – это расписание на 500 мс. Подсистема таймера использует собственный код.
  4. console.log("C") .
  5. Код входит в спиновый цикл.
  6. В какой-то момент времени часть пути через спин-контур предварительно установленный таймер готов к стрельбе. Решать, как это работает, зависит от реализации интерпретатора, но конечным результатом является то, что событие таймера вставляется в очередь событий Javascript.
  7. Цикл спина завершается.
  8. console.log("D") .
  9. Этот кусок Javascript заканчивается и возвращает управление системе.
  10. Интерпретатор Javascript видит, что текущий фрагмент Javascript выполнен таким образом, что он проверяет очередь событий, чтобы увидеть, ожидаются ли ожидающие события ожидания. Он находит событие таймера и обратный вызов, связанный с этим событием, и вызывает этот обратный вызов (запуск нового блока выполнения JS). Этот код запускается, и выводится console.log("B") .
  11. Этот callbackTimeout setTimeout() завершает выполнение, и интерпретатор снова проверяет очередь событий, чтобы увидеть, есть ли какие-либо другие события, которые готовы к запуску.

В то же время не может выполняться два вызова функции (узел b / c – однопоточный), поэтому это не будет проблемой. Единственная проблема – если в некоторых случаях async_task (..) отменяет обратный вызов. Но если, например, «async_task (..)» просто вызывал setTimeout (..) с данной функцией, то да, каждый вызов будет выполняться, они никогда не будут сталкиваться друг с другом, а «оценка» будет иметь ожидаемое значение , 'Произвольная_ длина', в конце.

Конечно, «произвольная длина» не может быть настолько велика, чтобы выходить из памяти, или переполнять любую коллекцию, удерживающую эти обратные вызовы. Однако проблема с потоками отсутствует.

  • Ошибка при попытке запустить пример теста selenium webdriver
  • Масштабирование Socket.IO для нескольких процессов Node.js с использованием кластера
  • Как связать выполнение массива функций, когда каждая функция возвращает отложенную.
  • Стамбульское покрытие кода для тестов Mocha
  • Как использовать событие стока stream.Writable в Node.js
  • Ошибка сборки Jenkin на установке npm
  • Как я могу ждать в Node.js (Javascript), мне нужно приостановить на некоторое время
  • Монгодб возвращает старую коллекцию
  • Как я могу редактировать свои файлы на сервере без перезагрузки nodejs, когда я хочу увидеть изменения?
  • Как отправить флеш-сообщения в Express 4.0?
  • Предотвратите замену Node.js на результат печати
  • Давайте будем гением компьютера.