Am I гарантирует, что окончательная стоимость нот будет равна arbitrary_length?
Да, пока все async_task()
вызовы вызовите функцию обратного вызова один раз и только один раз, вы гарантированно, что окончательная стоимость нот будет равна arbitrary_length.
Это однопоточный характер Javascript, который гарантирует, что в то же самое время никогда не будет два куска Javascript. Вместо этого из-за характера Javascript, управляемого событиями в обоих браузерах и node.js, одна часть JS завершается, затем следующее событие вытягивается из очереди событий и запускает обратный вызов, который также будет запущен до завершения.
Существует не такая вещь, как Javascript с прерыванием (где некоторая обратная связь может прервать часть другого Javascript, который в настоящее время работает). Все сериализуется через очередь событий. Это огромное упрощение и предотвращает много случаев, которые в противном случае были бы большой частью работы над безопасным программным обеспечением, когда у вас есть либо несколько потоков, работающих одновременно, либо управляемый прерыванием код.
Все еще есть проблемы с параллелизмом, которые могут быть затронуты, но они имеют больше общего с общим состоянием, доступ к которому могут иметь множественные асинхронные обратные вызовы. Хотя только один из них когда-либо будет обращаться к нему в любой момент времени, все же возможно, что часть кода, которая содержит несколько асинхронных операций, может оставить какое-то состояние в состоянии «между», пока оно находилось в середине нескольких асинхронных операций на где может выполняться другая операция async, и может попытаться получить доступ к этим данным.
Подробнее о событии, управляемом природой Javascript здесь: How does JavaScript handle AJAX responses in the background?, и этот ответ также содержит ряд других ссылок.
И другой подобный ответ, который обсуждает вид общих условий гонки данных, которые возможны: Can this code cause a race condition in socket io?
Некоторые другие ссылки:
how do I prevent event handlers to handle multiple events at once in javascript?
Do I need to be concerned with race conditions with asynchronous Javascript?
JavaScript - When exactly does the call stack become "empty"?
Node.js server with multiple concurrent requests, how does it work?
Чтобы дать вам представление о вопросах параллельности, которые могут произойти в Javascript (даже без резьбы и без прерываний, вот пример из моего собственного кода.
У меня есть сервер Raspberry Pi node.js, который управляет вентиляторами в моем доме. Каждые 10 секунд он проверяет два температурных датчика, один внутри чердака и один вне дома, и решает, как он должен управлять вентиляторами (через реле). Он также записывает данные о температуре, которые могут быть представлены в диаграммах. Один раз в час он сохраняет последние данные температуры, собранные в памяти, в некоторые файлы для сохранения в случае сбоя питания или сбоя сервера. Эта операция сохранения включает в себя ряд асинхронных файлов. Каждая из этих асинхронных записей возвращает управление системе, а затем продолжается, когда асинхронный обратный вызов называется завершением сигнализации. Поскольку это низкая система памяти, и данные могут потенциально занимать значительную часть доступной ОЗУ, данные не копируются в памяти перед записью (это просто непрактично). Итак, я записываю данные в режиме реального времени на диск.
В любое время в любой из этих операций ввода-вывода асинхронного файла, ожидая, что обратный вызов будет означать завершение многих операций с файлами, один из моих таймеров на сервере мог бы запустить, я бы собирал новый набор данных температуры и которые попытаются изменить набор данных в памяти, который я нахожу в середине написания. Это проблема параллелизма, ожидающая своего появления. Если он меняет данные, пока я написал часть его, и я жду, когда эта запись закончится, прежде чем писать все остальное, тогда полученные данные могут быть легко повреждены, потому что я выписал одну часть данных, данные будут изменены из-под меня, а затем я попытаюсь выписать больше данных, не осознавая, что они были изменены. Это проблема параллелизма.
На самом деле у меня есть оператор 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 до тех пор, пока не вернет управление системе, а затем (и только тогда), он выберет следующее событие из очереди событий и вызовет обратный вызов, связанный с этим событием.
Ниже представлена последовательность событий под обложками.
- Этот JS начинает работать.
console.log("A")
- выход.
- Событие таймера - это график на 500 мс с этого момента. Подсистема таймера использует собственный код.
console.log("C")
- выход.
- Код вводится в контур вращения.
- В какой-то момент времени на части через спин-контур предварительно установленный таймер готов к срабатыванию. Решать, как это работает, зависит от реализации интерпретатора, но конечным результатом является то, что событие таймера вставляется в очередь событий Javascript.
- Цикл спина завершается.
console.log("D")
- выход.
- Этот кусок Javascript заканчивается и возвращает управление обратно в систему.
- Интерпретатор Javascript видит, что текущий фрагмент Javascript сделан, поэтому он проверяет очередь событий, чтобы увидеть, ожидаются ли ожидающие события ожидания. Он находит событие таймера и обратный вызов, связанный с этим событием, и вызывает этот обратный вызов (запуск нового блока выполнения JS). Этот код начинает работать и выводится
console.log("B")
.
- Этот
setTimeout()
callback завершает выполнение, и интерпретатор снова проверяет очередь событий, чтобы увидеть, есть ли какие-либо другие события, которые готовы к запуску.
«вызов одного обратного вызова при выполнении каждой задачи». Как я могу узнать, что все задачи выполнены. Я могу думать только о том, что занят до тех пор, пока не заработает счет! = Произвольный_length – bilalba
@bilalba Да, это все, что вы можете сделать. На самом деле этот код делает то же самое с длиной массива результатов. :-) Лучше кстати. добавить какой-то обработчик ошибок для отклоненных задач, иначе вы будете ждать вечности при возникновении ошибки. – inf3rno
«Как я могу узнать, что все задачи выполнены» <- Это точная причина, по которой «Promise.all()» был реализован. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all –