2010-04-29 2 views
525

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

+22

В контексте браузеров, возможно. Но некоторые программы позволяют рассматривать JS как язык верхнего уровня и предоставлять привязки для других C++-библиотек. Например, flusspferd (привязки C++ для JS - AWESOME BTW) делал некоторые вещи с многопоточным JS. Это зависит от контекста. –

+3

Это должен быть прочитан: https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop – RickyA

ответ

500

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

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

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

Однако в действительности это ISN «Совершенно верно, в подлых проворных путях.

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

<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 и вы получите модальное диалоговое окно. Больше не выполняется скрипт, пока вы не уволите этот диалог, да? Неа. Измените размер главного окна, и вы получите alert in, resize, alert out в текстовом поле.

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

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

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

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

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

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

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

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

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

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

+6

Я не понимаю проблему с поведением фокуса/размытия - для меня это кажется вполне ожидаемым. Когда что-то сфокусировано или размыто, я хочу знать как можно скорее. Но я понимаю, почему вы упомянули об этом - это относится к теме :) – James

+13

@JP: Лично я не хочу знать сразу, потому что это означает, что я должен быть осторожным, чтобы мой код был повторным, что вызов моего кода размытия не повлияет на состояние, на которое опирается какой-то внешний код. Слишком много случаев, когда размытие является неожиданным побочным эффектом, чтобы обязательно поймать каждого. И, к сожалению, даже если вы это сделаете, это не так! IE запускает 'blur' * после * ваш код возвращает управление браузеру. – bobince

+2

По моему опыту, при манипулировании DOM возникает нечетное поведение. Кажется, я вспоминаю проблему, с которой я столкнулся, когда я предположил, что после манипулирования множеством элементов из DOM в функции они будут удалены/добавлены, но не так, когда я позвонил следующая функция укорачивания:/ –

13

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

+1

hehe - красиво положить. :-) –

97

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

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

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

+19

Согласен. Если в Javascript в браузере добавляется многопоточность, он будет через некоторый явный API (например, Web Workers), точно так же, как и с обязательными языками * all *. Это единственный способ, который имеет смысл. –

+0

Сэр, можем ли мы поговорить, у меня есть некоторые сомнения. Это будет отличная помощь –

2

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

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

10

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

6

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

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

7

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

-4

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

+0

Как вы вставляете setTimeouts? Например, пожалуйста ... – nnnnnn

+5

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

4

No.

Я иду против толпы здесь, но несут с собой. Один 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 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; 
} 

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

+2

Большинство определений языков предназначены для эффективной однопоточной , и указать, что многопоточность разрешена до тех пор, пока эффект идентичен. (например, UML) – jevon

+0

Я должен согласиться с ответом просто потому, что текущий ECMAScript не предусматривает * никакого положения * (хотя, возможно, я думаю, что то же самое можно сказать для C) для параллельных * контекстов выполнения ECMAScript *. Затем, как и этот ответ, я бы сказал, что любая реализация, которая имеет параллельные потоки, способные изменять общее состояние, является расширением ECMAScript *. – user2864740

2

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

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

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

1

Я попытался @ пример 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 

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

0

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

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

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

Я использую 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 ...

+0

Я постараюсь ответить самому себе. После [некоторых тестов] (https://jsfiddle.net/La4zc2b3/) кажется, что 'dispatchEvent' запускает только триггеры« Capturing »и« At Target »[этапы] (https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase) в потоке событий. Таким образом, невозможно зафиксировать это событие на фазе «Bubbling» (которая является фазой по умолчанию в 'addEventListener', если вы не установите третий параметр в значение true). – srus

4

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

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

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

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

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

От чтения Rhino документации я делаю вывод, что это может быть возможно для кого-то, чтобы написать JavaScript API, который также порождает новые JavaScript темы, но апи будет носорога-специфический (например, узел может только породить новый процесс) ,

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

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

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

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

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

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

Смежные вопросы