Это JavaScript гарантированно однопоточный?

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

12 ответов


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

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

(*: игнорирование вопроса о том, действительно ли браузеры реализуют свои 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() звонок unfocuses, и заказа журнала log in, change, blur, log out, за исключением оперы, где это log in, blur, log out, change и т. е. где это (даже меньше explicably) log in, change, log out, blur.

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

(я использую direct 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 и вы получите модальное диалоговое окно. Сценарий больше не выполняется, пока вы не отклоните этот диалог, Да? Нет. Размер главное окно и вы получите alert in, resize, alert out в textarea.

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

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

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

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

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

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

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

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

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

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


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

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

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


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


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


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

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


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


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

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

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

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

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

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

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

по поводу браузеров и nodejs как я вижу это:

  • выполняется ли весь код js в одном потоке? : Утвердительный ответ.

  • может ли код js вызвать запуск других потоков? : Утвердительный ответ.

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

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


нет.

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

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

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 VMs, поскольку в некоторых ситуациях он может предложить большую скорость.

принимая эту концепцию дальше, возможно, что код может быть аннотирован, чтобы 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 однопоточный по традиции.


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

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

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


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

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


Я пробовал пример @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, close Alert popup и делаете "один поток", вы должны увидеть что-то вроде этого:

click begin
click end
result = 20, should be 20

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

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

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


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