JavaScript гарантированно будет однопоточным?

611

Известно, что JavaScript является однопоточным во всех современных реализациях браузеров, но указано ли это в каком-либо стандарте или это просто по традиции? Совершенно безопасно предположить, что JavaScript всегда однопоточный?

Егор Павлихин
источник
25
В контексте браузеров, наверное. Но некоторые программы позволяют вам рассматривать JS как язык верхнего уровня и предоставлять привязки для других библиотек C ++. Например, flusspferd (привязки C ++ для JS - AWESOME BTW) делал кое-что с многопоточным JS. Это зависит от контекста.
НГ.
11
Это необходимо прочитать: developer.mozilla.org/en/docs/Web/JavaScript/EventLoop
RickyA

Ответы:

583

Это хороший вопрос. Я хотел бы сказать «да». Я не могу

Считается, что JavaScript имеет единственный поток выполнения, видимый для сценариев (*), поэтому при вводе встроенного сценария, прослушивателя событий или тайм-аута вы полностью контролируете его до тех пор, пока не вернетесь с конца своего блока или функции.

(*: игнорирование вопроса о том, действительно ли браузеры реализуют свои движки JS, используя один поток ОС, или WebWorkers вводят другие ограниченные потоки выполнения.)

Тем не менее, на самом деле это не совсем верно , в подлых противных отношениях.

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

var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
    l.value+= 'blur\n';
};
setTimeout(function() {
    l.value+= 'log in\n';
    l.focus();
    l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

Результаты log in, blur, log outна всех, кроме IE. Эти события не просто запускаются, потому что вы вызвали focus()напрямую, они могут произойти, потому что вы позвонили alert(), открыли всплывающее окно или что-то еще, что перемещает фокус.

Это также может привести к другим событиям. Например, добавьте i.onchangeпрослушиватель и введите что-нибудь во входные данные до того, как focus()вызов не сфокусирует его, и порядок журналов будет таким log in, change, blur, log out, за исключением Opera, где он есть, log in, blur, log out, changeи IE, где он есть (даже менее объяснимо) log in, change, log out, blur.

Точно так же вызов click()элемента, который обеспечивает его, вызывает onclickобработчик сразу во всех браузерах (по крайней мере, это согласованно!).

(Я использую on...свойства прямого обработчика событий здесь, но то же самое происходит с addEventListenerи attachEvent.)

Существует также множество обстоятельств, при которых события могут запускаться, пока ваш код пронизан, несмотря на то, что вы ничего не сделали, чтобы спровоцировать его. Пример:

var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
    l.value+= 'alert in\n';
    alert('alert!');
    l.value+= 'alert out\n';
};
window.onresize= function() {
    l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

Нажмите, alertи вы получите модальное диалоговое окно. Больше сценарий не выполняется, пока вы не закроете этот диалог, да? Нет. Измените размер главного окна, и вы попадете alert in, resize, alert outв текстовую область.

Вы можете подумать, что невозможно изменить размер окна, пока модальное диалоговое окно включено, но это не так: в Linux вы можете изменять размер окна так, как вам нравится; в Windows это не так просто, но вы можете сделать это, изменив разрешение экрана с большего на меньшее, где окно не помещается, в результате чего его размер изменяется.

Вы можете подумать, что только resize(и, возможно, еще несколько scroll) может срабатывать, когда пользователь не имеет активного взаимодействия с браузером, потому что сценарий является многопоточным. И для одиночных окон вы можете быть правы. Но это все подходит к концу, как только вы выполняете межоконный сценарий. Для всех браузеров, кроме Safari, который блокирует все окна / вкладки / фреймы, когда какой-либо из них занят, вы можете взаимодействовать с документом из кода другого документа, работая в отдельном потоке выполнения и вызывая любые связанные обработчики событий для Пожар.

Места, в которых события, которые вы можете вызвать, могут быть вызваны, пока скрипт все еще находится в потоке:

  • когда модальные всплывающие окна ( alert, confirm, prompt) открыты во всех браузерах , но Opera;

  • в showModalDialogбраузерах, которые его поддерживают;

  • диалоговое окно «Сценарий на этой странице может быть занят ...», даже если вы решите продолжить выполнение сценария, позволяет запускать такие события, как изменение размера и размытие, и обрабатывать их, даже когда сценарий находится в середине занятый цикл, кроме как в Опере.

  • Некоторое время назад для меня в IE с плагином Sun Java вызов любого метода в апплете мог позволить запускать события и повторно вводить сценарий. Это всегда была ошибка, чувствительная ко времени, и, возможно, с тех пор Sun исправила ее (я, конечно, на это надеюсь).

  • наверное больше. Прошло много времени с тех пор, как я протестировал это, и с тех пор браузеры приобрели сложность

Таким образом, для большинства пользователей JavaScript, как правило, имеет строгий, управляемый событиями единый поток выполнения. На самом деле такого не бывает. Непонятно, насколько это просто ошибка, а какой умышленный дизайн, но если вы пишете сложные приложения, особенно с кросс-оконными / фрейм-скриптовыми сценариями, есть все шансы, что они могут вас укусить - и периодически, трудные для отладки способы.

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

bobince
источник
14
@JP: Лично я не хочу знать сразу, потому что это означает, что я должен быть осторожным, чтобы мой код был повторным, чтобы вызов моего кода размытия не влиял на состояние, на которое полагается некоторый внешний код. Есть слишком много случаев, когда размытие является неожиданным побочным эффектом, чтобы обязательно поймать каждого. И, к сожалению, даже если вы этого хотите, это ненадежно! IE запускается blur после того, как ваш код возвращает управление браузеру.
Бобинц
107
Javascript является однопоточным. Остановка выполнения в alert () не означает, что поток событий прекращает накачивать события. Просто означает, что ваш сценарий спит, пока предупреждение находится на экране, но он должен продолжать прокачивать события, чтобы нарисовать экран. Пока выдается предупреждение, насос событий работает, что означает, что правильно отправлять события. В лучшем случае это демонстрирует совместную многопоточность, которая может происходить в javascript, но все это поведение может быть объяснено функцией, просто добавляющей событие к насосу событий для последующей обработки, а не делающим это сейчас.
chubbsondubs
34
Но помните, что совместная многопоточность все еще однопоточна. Две вещи не могут происходить одновременно, что позволяет многопоточность и вводит недетерминизм. Все, что было описано, является детерминированным, и это хорошее напоминание об этих типах проблем. Хорошая работа по анализу @bobince
chubbsondubs
94
Чаббард прав: JavaScript однопоточный. Это не пример многопоточности, а синхронная отправка сообщений в одном потоке. Да, можно приостановить стек и продолжить отправку события (например, alert ()), но проблемы с доступом, которые возникают в реальных многопоточных средах, просто не могут возникнуть; например, вы никогда не будете иметь переменные значения изменения между вами между тестом и непосредственно последующим назначением, потому что ваш поток не может быть произвольно прерван. Я боюсь, что этот ответ только вызовет замешательство.
Крис Гизинг
19
Да, но учитывая, что блокирующая функция, которая ожидает пользовательского ввода, может происходить между любыми двумя операторами, у вас потенциально есть все проблемы согласованности, которые доставляют вам потоки уровня ОС. То, работает ли движок JavaScript на самом деле в нескольких потоках ОС, не имеет большого значения.
bobince
115

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

Добавьте к этому тот факт, что HTML5 уже определяет Web Workers (явный стандартизированный API для многопоточного кода JavaScript), вводя многопоточность в базовый Javascript, было бы в основном бессмысленно.

( Примечание для других комментаторов: несмотря на то setTimeout/setInterval, что события загрузки по HTTP-запросу (XHR) и события пользовательского интерфейса (щелчок, фокус и т. Д.) Дают общее представление о многопоточности - все они все выполняются по одной временной шкале - по одному в время - поэтому, даже если мы не знаем заранее порядок их выполнения, нет необходимости беспокоиться об изменении внешних условий во время выполнения обработчика событий, функции времени или обратного вызова XHR.)

Мар Эрлигссон
источник
21
Согласен. Если многопоточность когда-либо добавляется в Javascript в браузере, это будет происходить через некоторый явный API (например, Web Workers), как это происходит со всеми обязательными языками. Это единственный способ, который имеет смысл.
Дин Хардинг
1
Обратите внимание, что есть один. основной поток JS, НО некоторые вещи выполняются в браузере параллельно. Это не просто впечатление многопоточности. Запросы фактически выполняются параллельно. Слушатели, которые вы определяете в JS, запускаются один за другим, но запросы действительно параллельны.
Nux,
16

Да, хотя при использовании любого из асинхронных API-интерфейсов, таких как setInterval и xmlhttp, могут возникнуть некоторые проблемы параллельного программирования (в основном, состояния гонки).

расточитель
источник
10

Да, хотя Internet Explorer 9 скомпилирует ваш Javascript в отдельном потоке для подготовки к выполнению в основном потоке. Это ничего не меняет для вас, как для программиста.

ChessWhiz
источник
8

Я бы сказал, что спецификация не препятствует тому, чтобы кто-то создавал движок, который запускает javascript в нескольких потоках , требуя, чтобы код выполнял синхронизацию для доступа к общему состоянию объекта.

Я думаю, что однопоточная неблокирующая парадигма возникла из-за необходимости запускать javascript в браузерах, где пользовательский интерфейс никогда не должен блокировать.

Nodejs следовал подходу браузеров .

Однако движок Rhino поддерживает запуск кода js в разных потоках . Выполнения не могут совместно использовать контекст, но они могут совместно использовать область. Для этого конкретного случая в документации говорится:

... "Rhino гарантирует, что доступ к свойствам объектов JavaScript является атомарным в разных потоках, но больше не дает никаких гарантий для сценариев, выполняющихся в одной и той же области одновременно. Если два сценария используют одну и ту же область одновременно, сценарии отвечает за координацию любого доступа к общим переменным . "

Из прочтения документации Rhino я заключаю, что кто-то может написать javascript api, который также порождает новые потоки javascript, но api будет специфичным для rhino (например, node может порождать только новый процесс).

Я предполагаю, что даже для движка, который поддерживает несколько потоков в javascript, должна быть совместимость со сценариями, которые не учитывают многопоточность или блокировку.

Что касается браузеров и nodejs , я вижу это так:

    1. Является ли все JS исполняемого кода в одном потоке ? : Да.
    1. Может ли js- код вызывать запуск других потоков ? : Да.
    1. Могут ли эти потоки изменить контекст выполнения js ?: Нет. Но они могут (прямо / косвенно (?)) Добавлять в очередь событий, из которой слушатели могут изменять контекст выполнения . Но не обманывайте себя, слушатели снова атомарно бегут по основному потоку .

Так, в случае браузеров и nodejs (и, вероятно, многих других движков) javascript не является многопоточным, а сами движки .


Обновление о веб-работниках:

Присутствие веб-работников также оправдывает то, что javascript может быть многопоточным, в том смысле, что кто-то может создавать код в javascript, который будет работать в отдельном потоке.

Тем не менее, веб-работники не решают проблемы традиционных потоков, которые могут совместно использовать контекст выполнения. Правила 2 и 3 выше все еще применяются , но на этот раз многопоточный код создается пользователем (js code writer) в javascript.

Единственное, что нужно учитывать, это количество порожденных потоков с точки зрения эффективностине параллелизма). точки зрения ). См. ниже:

О безопасности потока :

Интерфейс Worker порождает реальные потоки уровня ОС, и внимательные программисты могут быть обеспокоены тем, что параллелизм может вызвать «интересные» эффекты в вашем коде, если вы не будете осторожны.

Однако, поскольку веб-работники тщательно контролируют точки связи с другими потоками, на самом деле очень трудно вызвать проблемы параллелизма . Нет доступа к сторонним компонентам или DOM. И вы должны передавать определенные данные в и из потока через сериализованные объекты. Таким образом, вы должны очень усердно работать, чтобы вызвать проблемы в вашем коде.


PS

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

Маринос Ан
источник
7

JavaScript / ECMAScript предназначен для жизни в среде хоста. То есть JavaScript на самом деле ничего не делает не если только хост-среда не решит проанализировать и выполнить данный скрипт и предоставить объекты среды, которые позволят JavaScript на самом деле быть полезным (например, DOM в браузерах).

Я думаю, что данная функция или блок скрипта будут выполняться построчно, и это гарантировано для JavaScript. Однако, возможно, среда хоста может выполнять несколько сценариев одновременно. Или среда хоста всегда может предоставить объект, который обеспечивает многопоточность. setTimeoutи setIntervalявляются примерами или, по крайней мере, псевдо-примерами среды хоста, обеспечивающей способ выполнения некоторого параллелизма (даже если это не совсем параллелизм).

боб
источник
7

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

Kennebec
источник
6

@Bobince дает действительно непрозрачный ответ.

Вопреки ответу Мара Орлигссона, Javascript всегда однопоточный из-за этого простого факта: все в Javascript выполняется по одной временной шкале.

Это строгое определение однопоточного языка программирования.

wle8300
источник
4

Нет.

Я иду против толпы здесь, но терпите меня. Один JS-скрипт должен быть эффективно однопоточным, но это не значит, что его нельзя интерпретировать по-разному.

Допустим, у вас есть следующий код ...

var list = [];
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Это написано с расчетом на то, что к концу цикла список должен иметь 10000 записей, которые являются квадратом индекса, но ВМ может заметить, что каждая итерация цикла не влияет на другую, и переосмыслить, используя два потока.

Первая нить

for (var i = 0; i < 5000; i++) {
  list[i] = i * i;
}

Вторая нить

for (var i = 5000; i < 10000; i++) {
  list[i] = i * i;
}

Я упрощаю здесь, потому что массивы JS более сложны, чем тупые куски памяти, но если эти два сценария способны добавлять записи в массив потокобезопасным способом, то к тому времени, когда оба будут выполнены, выполнение будет иметь тот же результат, что и однопоточная версия.

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

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

// like "use strict" this enables certain features on compatible VMs.
"use parallel";

var list = [];

// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Поскольку Web Workers приходят в Javascript, маловероятно, что эта ... более уродливая система когда-либо появится, но я думаю, можно с уверенностью сказать, что Javascript является однопоточным по традиции.

Максимум
источник
2
Хотя большинство определений языка разработано так, чтобы они были эффективно однопоточными, они утверждают, что многопоточность разрешена, если эффект одинаков. (например, UML)
Джевон
1
Я должен согласиться с ответом просто потому, что текущий ECMAScript не предусматривает (хотя я думаю, что то же самое можно сказать и о C) для одновременных контекстов выполнения ECMAScript . Затем, как и этот ответ, я бы сказал, что любая реализация, в которой параллельные потоки могут изменять общее состояние, является расширением ECMAScript .
user2864740
3

Ну, Chrome является многопроцессорным, и я думаю, что каждый процесс имеет собственный код Javascript, но, насколько известно, он «однопоточный».

В Javascript нет никакой поддержки многопоточности, по крайней мере, явно, так что это не имеет значения.

Франсиско Сото
источник
2

Я попробовал пример @ bobince с небольшими изменениями:

<html>
<head>
    <title>Test</title>
</head>
<body>
    <textarea id="log" rows="20" cols="40"></textarea>
    <br />
    <button id="act">Run</button>
    <script type="text/javascript">
        let l= document.getElementById('log');
        let b = document.getElementById('act');
        let s = 0;

        b.addEventListener('click', function() {
            l.value += 'click begin\n';

            s = 10;
            let s2 = s;

            alert('alert!');

            s = s + s2;

            l.value += 'click end\n';
            l.value += `result = ${s}, should be ${s2 + s2}\n`;
            l.value += '----------\n';
        });

        window.addEventListener('resize', function() {
            if (s === 10) {
                s = 5;
            }

            l.value+= 'resize\n';
        });
    </script>
</body>
</html>

Итак, когда вы нажимаете Run, закрываете всплывающее окно оповещения и делаете «один поток», вы должны увидеть что-то вроде этого:

click begin
click end
result = 20, should be 20

Но если вы попытаетесь запустить это в стабильной версии Opera или Firefox в Windows и свернуть / свернуть окно с всплывающим предупреждением на экране, то будет что-то вроде этого:

click begin
resize
click end
result = 15, should be 20

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

Антон Александренок
источник
-4

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

Джеймс
источник
5
chrome делает это правильно, не знаю, где @James видит, что он многопоточный ...: setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(для записи, yo dawg должен ВСЕГДА идти первым, а затем выводить консольный журнал)
Tor Valamo