Является ли JavaScript гарантированным однопоточным?

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

Ответ 1

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

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

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

Однако, на самом деле это не совсем так, в подлых проворных путях.

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

<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">
<script type="text/javascript">
    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();
</script>

Результаты в 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.)

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

<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>
<script type="text/javascript">
    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';
    };
</script>

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

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

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

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

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

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

  • диалоговое окно "A script на этой странице может быть занято...", даже если вы решите позволить script продолжать работать, позволяет запускать события, такие как изменение размера и размытие, и их обработка даже когда script находится в середине цикла занятости, за исключением Opera.

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

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

Nodejs выполнил подход браузеров.

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

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

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

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

Признание браузеров и nodejs так, как я вижу:

  • Выполняется ли все js-код в одном потоке?: Да.

  • Может ли код js запускать другие потоки?: Да.

  • Могут ли эти потоки мутировать js-код выполнения?: Нет. Но они могут (прямо/косвенно (?)) присоединяться к очереди событий.

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

Ответ 8

Нет.

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

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

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

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

Первая тема

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

Вторая нить

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

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

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

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

// 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;
}

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

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

Я попробовал пример @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>

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

click begin
click end
result = 20, should be 20

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

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

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

Ответ 12

Относится к этой теме. Я немного запутался в модели concurrency в JavaScript:

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

Итак, если среда выполнения выполняет обратный вызов (callback1) и внутренний callback1, вы отправляете событие (с связанным обратным вызовом2), завершает ли среда выполнения текущий обратный вызов1 перед запуском callback2? Как видно из моих тестов, ответ нет. Что немного страшно (условия гонки)...

Я использую dispathEvent для запуска пользовательского события в этих тестах. Имеет ли dispatchEvent какое-то "предпочтение" для среды выполнения?

Демо:

  var x = -1;

  window.addEventListener('load', handleLoad);
  document.addEventListener('event', handleEvent);

  function handleLoad() {
    x = 0;
    document.dispatchEvent(new Event('event'));
    x = 1;
  }

  function handleEvent() {
    x = 2;
  }

После выполнения x равно 1. Но если MDN docs верны, вы должны ожидать 2...

Ответ 13

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