Немного опоздал, но я просто задал себе тот же вопрос и придумал следующий ответ:
Javascript в браузерах всегда однопоточный , и фундаментальным следствием этого является то, что «одновременный» доступ к переменным (основная головная боль многопоточного программирования) на самом деле не параллелен; это верно за исключением веб-работников , которые на самом деле работают в отдельных потоках , и одновременный доступ к переменным должен рассматриваться в несколько явном виде .
Я не ниндзя JavaScript, но я также был убежден, что JavaScript в браузере предоставляется как однопоточный процесс, не обращая большого внимания на то, был ли он верным, или на обоснование этого убеждения.
Простой факт, который поддерживает это предположение, заключается в том, что при программировании на JavaScript вам не нужно заботиться о параллельном доступе к общим переменным . Каждый разработчик, даже не задумываясь о проблеме, пишет код, как будто каждый доступ к переменной согласован.
Другими словами, вам не нужно беспокоиться о так называемой модели памяти .
На самом деле нет необходимости смотреть на WebWorkers, чтобы задействовать параллельную обработку в JavaScript. Подумайте о (асинхронном) AJAX-запросе. И подумайте, как небрежно вы будете обращаться с одновременным доступом к переменным:
var counter = 0;
function asyncAddCounter() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
counter++;
}
};
xhttp.open("GET", "/a/remote/resource", true);
xhttp.send();
}
asyncAddCounter();
counter++;
Какое значение counter
в конце процесса? Это 2
.
Неважно, что он читается и пишется «одновременно», это никогда не приведет к 1
. Это означает, что доступ к counter
всегда согласован.
Если два потока действительно обращаются к значению одновременно, они оба могут начать с чтения 0
, и оба пишут 1
в конце.
В браузерах фактическая выборка данных удаленного ресурса скрыта для разработчика, и его внутренняя работа выходит за рамки JavaScript API (то, что браузер позволяет вам контролировать с помощью инструкций JavaScript). Что касается разработчика, то результат сетевого запроса обрабатывается основным потоком.
Короче говоря, фактическое выполнение запроса не отображается, но вызов обратного вызова (обработка результата с помощью пользовательского кода JavaScript) выполняется основным потоком.
Возможно, если бы не веб-разработчики, термин «многопоточность» никогда бы не вошел в мир Javascript.
Выполнение запроса и асинхронный вызов обратного вызова фактически достигаются с помощью циклов событий , а не многопоточности. Это верно для нескольких браузеров и, очевидно, для Node.js. Ниже приведены некоторые ссылки, в некоторых случаях немного устаревшие, но я думаю, что основная идея все еще сохраняется в настоящее время.
Этот факт является причиной, по которой JavaScript называется Управляемым событиями , но не многопоточным.
Обратите внимание, что, таким образом, JavaScript допускает асинхронные идиомы, но не параллельное выполнение кода JavaScript (вне веб-работников). Термин асинхронный просто обозначает тот факт, что результат двух инструкций может быть обработан в зашифрованном порядке.
Что касается WebWorkers, - это API-интерфейсы JavaScript, которые предоставляют разработчику контроль над многопоточным процессом.
Таким образом, они предоставляют явные способы обработки одновременного доступа к совместно используемой памяти (чтение и запись значений в разных потоках), и это делается, среди прочего, следующими способами:
- вы отправляете данные веб-работнику (что означает, что новый поток читает данные) с помощью структурированного клона: Алгоритм структурированного клона - Web APIs |MDN .По сути, нет «разделяемой» переменной, вместо этого новому потоку дается свежая копия объекта.
- вы передаете данные веб-работнику, передавая владение значением: Transferable - Web APIs |MDN .Это означает, что только один поток может прочитать его значение в любое время.
- Что касается результатов, возвращаемых веб-работниками (как они «пишут»), основной поток получает доступ к результатам при появлении соответствующего запроса (например с инструкцией
thisWorker.onmessage = function(e) {console.log('Message ' + e.data + ' received from worker');}
).Должно быть, это происходит с помощью обычного цикла обработки событий. - основной поток и веб-работник получают доступ к действительно общей памяти,
SharedArrayBuffer
, к которой можно безопасно обращаться по потокам с помощью Atomic
функции.Я обнаружил, что это ясно видно в этой статье: JavaScript: от рабочих к общей памяти - примечание: веб-работники не могут получить доступ к DOM, который действительно является общим!