JavaScript保证是单线程的?
在所有的现代浏览器实现中,JavaScript都是单线程的,但在任何标准中都是如此,还是仅仅是传统的? 假设JavaScript总是单线程是完全安全的吗?
这是一个很好的问题。 我很乐意说“是”。 我不能。
JavaScript通常被认为有一个对脚本(*)可见的执行线程,所以当你input你的内联脚本,事件监听器或者超时时,你仍然完全处于控制状态,直到你从块或函数结束返回。
(*:忽略浏览器是否真的使用一个OS线程来实现他们的JS引擎,或者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()
或者打开了一个popup窗口,或者其他任何移动焦点的事情而发生的。
这也可能导致其他事件。 例如,添加一个i.onchange
侦听器,并在focus()
调用未聚焦之前在input中input内容,并且日志顺序是log in, change, blur, log out
,除了在Opera中log in, blur, log out, change
和IE它(更不用说明) log in, change, log out, blur
。
类似地,在提供它的元素上调用click()
立即在所有浏览器中调用onclick
处理程序(至less这是一致的!)。
(我在这里使用直接on...
事件处理程序属性,但addEventListener
和attachEvent
。
在你的代码被插入的时候,还有很多事件可以被触发,尽pipe你没有做任何事情来挑起它。 一个例子:
<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。
您可能会认为在modal dialog启动时调整窗口大小是不可能的,但并不是这样:在Linux中,您可以随意调整窗口大小; 在Windows上并不是那么容易,但是可以通过将屏幕分辨率从较大的屏幕分辨率更改为较小的屏幕分辨率来实现。
您可能会认为,只有在用户没有与浏览器进行主动交互时,才会触发resize
(也可能更像scroll
),因为脚本是线程化的。 而对于单一的窗户,你可能是对的。 但是,一旦你做了跨窗口的脚本,这一切都会发生。 对于除Safari以外的所有浏览器,当任何一个浏览器忙于任何一个窗口/标签页/框架时,您可以与另一个文档代码中的文档进行交互,在单独的执行线程中运行,并导致任何相关的事件处理程序火。
当脚本仍然处于线程状态时,可以引发可以导致生成事件的位置:
-
当模式popup窗口(
alert
,confirm
,prompt
)打开时,在所有的浏览器中,但Opera; -
在支持它的浏览器上
showModalDialog
期间; -
即使您select让脚本继续运行,“在此页面上的脚本可能正在忙碌…”对话框中,即使在脚本处于中间位置时,也允许重新resize和模糊等事件并进行处理繁忙循环,除了Opera。
-
前一段时间对于我来说,在IE中使用Sun Java插件,调用applet上的任何方法都可以允许事件触发并重新input脚本。 这总是一个时间敏感的错误,Sun可能已经修复它(我当然希望如此)。
-
可能更多。 自从我testing这个以来,已经有一段时间了,浏览器已经变得复杂了。
总之,大多数情况下,JavaScript对大多数用户来说都具有严格的事件驱动的单线程执行。 事实上,它没有这样的事情。 目前还不清楚这其中有多less是一个错误和多less刻意的devise,但是如果你正在编写复杂的应用程序,尤其是跨窗口/框架脚本的应用程序,那么它有可能咬你的机会 – 难以debugging的方式。
如果最糟糕的情况出现,那么可以通过指向所有事件响应来解决并发问题。 当事件进入时,将其放入队列中,然后在setInterval
函数中处理队列。 如果您正在编写一个您打算被复杂应用程序使用的框架,那么这样做可能是一个好的举措。 postMessage
也将有望缓解未来跨文档脚本的痛苦。
我会说是的 – 因为几乎所有现有的(至less所有非平凡的)JavaScript代码将会中断,如果浏览器的JavaScript引擎asynchronous运行它。
除此之外, HTML5已经为multithreading代码引入了multithreading技术的Web Workers (一种明确的,multithreading的JavaScript代码的标准化API)的事实将毫无意义。
( 注意到其他评论者:尽pipesetTimeout/setInterval
,HTTP请求onload事件(XHR)和UI事件(click,focus等)提供了multithreading的粗略印象 – 它们仍然全部在单个时间线上执行 – 一次一个 – 所以即使我们事先不知道它们的执行顺序,也不必担心在执行事件处理程序,定时函数或XHRcallback期间外部条件的改变。)
是的,尽pipe在使用任何asynchronousAPI(如setInterval和xmlhttpcallback函数)时仍然会遇到一些并发编程问题(主要是竞争条件)。
是的,尽pipeInternet Explorer 9会在单独的线程上编译您的Javascript,以准备在主线程上执行。 不过,这对于程序员来说并没有什么改变。
实际上,父窗口可以与子窗口或同级窗口或具有自己的执行线程的框架进行通信。
JavaScript / ECMAScript被devise为在主机环境中生活。 也就是说,除非宿主环境决定parsing和执行给定的脚本,并且提供了让JavaScript真正有用的环境对象(例如浏览器中的DOM),否则JavaScript实际上不会做任何事情 。
我认为一个给定的函数或脚本块将逐行执行,这是保证JavaScript。 但是,主机环境也许可以同时执行多个脚本。 或者,主机环境总是可以提供一个提供multithreading的对象。 setTimeout
和setInterval
是主机环境的例子,或者至less是伪例子,提供了一些方法去做一些并发(即使它不完全并发)。
没有。
我在这里反对人群,但忍耐着我。 一个单一的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数组比较笨,但是如果这两个脚本能够以线程安全的方式向数组添加条目,那么当两者都执行完毕后,与单线程版本相同的结果。
虽然我不知道任何虚拟机检测到这样的可并行化的代码,但它似乎可能在将来为JIT虚拟机而存在,因为它可以在某些情况下提供更快的速度。
进一步考虑这个概念,可能会对代码进行注释,让虚拟机知道要转换为multithreading代码。
// 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; }
既然networking工作者正在使用Javascript,那么这个丑陋的系统就不太可能出现,但我认为可以肯定的说Javascript是传统的单线程的。
我会说规范不会阻止某人创build一个在multithreading上运行javascript的引擎,要求代码执行同步访问共享对象状态。
我认为单线程的非阻塞模式来源于需要在浏览器中运行javascript的地方,而UI永远不会阻塞。
Nodejs遵循浏览器的方法。
犀牛引擎,但是, 支持在不同的线程运行js代码 。 执行不能共享上下文,但可以共享范围。 对于这个特定的情况,文档指出:
…“Rhino保证访问JavaScript对象的属性在线程中是primefaces的,但是不能同时在同一个作用域内执行脚本。如果两个脚本同时使用相同的作用域, 脚本是负责协调对共享variables的访问 。“
从阅读犀牛文档我得出结论,有人可能会写一个JavaScript API,也产生新的JavaScript线程,但API将是犀牛专用(例如节点只能产卵一个新的进程)。
我想,即使是一个支持JavaScript中的multithreading的引擎,应该与不考虑multithreading或阻塞的脚本兼容。
通过我看到的方式来了解浏览器和nodejs是:
-
所有的js代码是在一个线程中执行的吗? :是的。
-
js代码可以导致其他线程运行? :是的。
-
这些线程可以改变js执行上下文吗?但是他们可以(直接/间接地(?))追加到事件队列中。
所以,在浏览器和nodejs(可能还有很多其他引擎)的情况下,javascript不是multithreading的,而是引擎本身。
那么,Chrome是多进程的,我认为每个进程都处理自己的Javascript代码,但是就代码而言,它是“单线程的”。
对于multithreading来说,Javascript中没有任何支持,至less不是明确的,所以它没有任何区别。
@Bobince提供了一个非常不透明的答案。
对MárÖrlygsson的回答嗤之以鼻,因为这个简单的事实,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,closurespopup窗口并做一个“单线程”,你应该看到这样的东西:
click begin click end result = 20, should be 20
但是,如果您尝试在Windows上运行Opera或Firefox稳定版,并通过屏幕上的警告popup窗口最小化/最大化窗口,则会出现如下所示的内容:
click begin resize click end result = 15, should be 20
我不想说,这是“multithreading”,但是一些代码在错误的时间里执行了,并没有期待这个,现在我的状态已经被破坏了。 更好地了解这种行为。
相关的这个线程。 我对JavaScript中的并发模型有点困惑:
在单线程事件循环中处理任何其他事件之前是否完全处理每个事件? 正如你可以阅读MDN 。
所以,如果运行时正在执行一个callback(callback1),并且在callback1内部调度一个事件(带有一个关联的callback2),那么运行时是否会在开始执行callback2之前完成当前callback1的执行? 从我的testing中可以看出,答案是否定的 。 这有点吓人(比赛条件)…
我正在使用dispathEvent
在这些testing中触发自定义事件。 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文档是正确的,你应该期望2 …
试着将两个setTimeout函数嵌套在一起,它们将performance为multithreading(即:在执行其function之前,外部计时器不会等待内部函数完成)。