是否保证JavaScript是单线程的?


610

众所周知,JavaScript在所有现代浏览器实现中都是单线程的,但是它是在任何标准中指定的,还是仅根据传统?假定JavaScript始终是单线程的,是否完全安全?


25
在浏览器的上下文中,可能是这样。但是有些程序允许您将JS视为顶级lang并提供其他C ++库的绑定。例如,flusspferd(JS的C ++绑定-AWESOME BTW)正在使用多线程JS做一些事情。这取决于上下文。
NG。

Answers:


583

这是个好问题。我很想说“是”。我不能

通常认为JavaScript具有脚本(*)可见的单个执行线程,因此,当您输入内联脚本,事件侦听器或超时时,您将完全处于控制之下,直到从块或函数的末尾返回为止。

(*:忽略浏览器是否真的使用一个OS线程来实现其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...事件处理程序属性,但addEventListenerand 也会发生同样的情况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以外的所有浏览器,当它们中的任何一个忙时都将阻止所有窗口/选项卡/框架时,您可以与另一个文档的代码中的文档进行交互,在单独的执行线程中运行,并使所有相关的事件处理程序火。

在脚本仍被线程化的情况下,可以引发可能引发事件的地方:

  • 当模式弹出窗口(alertconfirmprompt)是开放的,在所有的浏览器,但歌剧;

  • showModalDialog支持它的浏览器中;

  • 即使您选择让脚本继续运行,“此页面上的脚本可能正忙于...”对话框也将允许触发大小调整和模糊之类的事件,即使该脚本位于脚本中间,也可以对其进行处理。忙循环,Opera除外。

  • 对我来说,前一段时间,在带有Sun Java Plugin的IE中,调用applet上的任何方法都可以触发事件并重新输入脚本。自始至终,这始终是一个对时间敏感的错误,Sun可能已经修复了此错误(我当然希望如此)。

  • 可能更多。自从我测试了这已经有一段时间了,浏览器也因此变得越来越复杂。

总之,大多数时候,JavaScript对大多数用户而言似乎具有严格的事件驱动的单线程执行。实际上,它没有这样的东西。尚不清楚其中有多少只是一个错误,有多少是经过深思熟虑的设计,但是如果您要编写复杂的应用程序,尤其是跨窗口/框架脚本的应用程序,那么它很有可能会咬住您-并且断断续续地,难以调试的方式。

如果情况变得最糟,则可以通过间接所有事件响应来解决并发问题。当事件进入时,将其放入队列中,然后在setInterval函数中按顺序处理该队列。如果要编写一个打算由复杂应用程序使用的框架,那么这样做可能是一个不错的选择。postMessage希望将来也能减轻跨文档脚本编写的痛苦。


14
@JP:我个人不想立即知道,因为这意味着我必须小心我的代码是可重入的,调用模糊代码不会影响某些外部代码所依赖的状态。在很多情况下,模糊是意想不到的副作用,以至于无法抓住每个人。而且不幸的是,即使您确实想要它,也不可靠!您的代码将控制权返回给浏览器blur 后, IE会触发。
bobince 2010年

107
Javascript是单线程的。停止对alert()的执行并不意味着事件线程停止泵送事件。只是意味着您的脚本在警报显示在屏幕上时处于休眠状态,但是它必须保持泵送事件才能绘制屏幕。发出警报时,事件泵正在运行,这意味着继续发送事件是完全正确的。充其量,这表明了可以在javascript中发生的协作线程,但是所有这些行为都可以通过一个函数来解释,该函数将事件简单地附加到事件泵中,以便稍后处理(与现在执行)不同。
chubbsondubs

34
但是,请记住,协作线程仍然是单线程。两件事不能同时发生,这就是多线程允许并注入不确定性的原因。描述的所有内容都是确定性的,这很好地提醒了这些类型的问题。分析工作不错@bobince
chubbsondubs 2010年

94
Chubbard是对的:JavaScript是单线程的。这不是多线程的示例,而是单线程中的同步消息分发。是的,可以暂停堆栈并继续进行事件分配(例如alert()),但是在真正的多线程环境中发生的访问问题根本不可能发生;这是可能的。例如,在测试和紧接着的后续分配之间,您将永远不会有变量更改值,因为您的线程不能被任意中断。我担心这种反应只会引起混乱。
克里斯·吉辛

19
是的,但是考虑到等待用户输入的阻塞功能可能在任何两个语句之间发生,您可能会遇到OS级线程带来的所有一致性问题。JavaScript引擎是否实际在多个OS线程中运行无关紧要。
bobince '02

115

我会说是-因为如果浏览器的javascript引擎异步运行它,则几乎所有现有(至少所有不重要的)javascript代码都会中断。

此外,HTML5已经指定了将Web多线程引入基本Javascript中的Web Workers(用于多线程javascript代码的显式,标准化API)这一事实几乎没有意义。

其他注释者的注意:即使setTimeout/setIntervalHTTP请求重载事件(XHR)和UI事件(单击,焦点等)也提供了多线程的粗略印象-它们仍然都是沿着单个时间轴执行的-时间-因此,即使我们事先不知道它们的执行顺序,也不必担心事件处理程序,定时函数或XHR回调的执行期间外部条件会发生变化。)


21
我同意。如果曾经在浏览器中向Javascript添加多线程,则它将通过某些显式API(例如Web Workers),就像所有命令式语言一样。那是唯一有意义的方法。
Dean Harding

1
请注意,只有一个。JS主线程,但某些事情在浏览器中并行运行。这不仅仅是对多线程的印象。请求实际上是并行运行的。您在JS中定义的侦听器是一对一运行的,但是请求实际上是并行的。
Nux

16

是的,尽管在使用任何异步API(例如setInterval和xmlhttp回调)时仍然会遇到一些并发编程问题(主要是竞争条件)。


10

是的,尽管Internet Explorer 9会在单独的线程上编译Javascript以准备在主线程上执行。但是,这对于您作为程序员来说并没有任何改变。


8

我要说的是,说明书中并没有防止有人从创建发动机运行 JavaScript的上多个线程,要求代码以访问共享对象状态执行同步。

我认为单线程非阻塞范例是出于在ui永远不会阻塞的浏览器中运行javascript的需要。

Nodejs遵循了浏览器的方法

但是Rhino引擎支持在不同线程中运行js代码。执行不能共享上下文,但是可以共享作用域。对于这种特定情况,文档说明:

...” Rhino保证跨线程访问JavaScript对象的属性是原子的,但不再保证同时在同一作用域中执行的脚本。如果两个脚本同时使用同一作用域,则这些脚本是负责协调对共享变量的所有访问。”

通过阅读Rhino文档,我得出的结论是,有人可以编写也产生新的JavaScript线程的javascript API,但是该api是特定于Rhino的(例如,节点只能产生新的进程)。

我想即使对于支持JavaScript中多个线程的引擎,也应该与不考虑多线程或阻塞的脚本兼容。

Concearning 浏览器的NodeJS的方式,我看到它是:

    1. 是否所有js代码都在单个线程中执行?:是的。
    1. JS代码造成其他线程运行?:是的。
    1. 这些线程可以改变js执行上下文吗?:否。但是它们可以(直接/间接(?))追加到事件队列中,侦听器可以从 事件队列改变执行上下文。但不要上当,监听器会再次在主线程上自动运行。

因此,在使用浏览器和Node.js(可能还有许多其他引擎)的情况下,javascript不是多线程的,而引擎本身是


有关网络工作者的更新:

网络工作人员的存在进一步证明了javascript可以是多线程的,因为有人可以在javascript中创建将在单独的线程上运行的代码。

但是:网络工作者不会担心可以共享执行上下文的传统线程的问题上面的规则2和3仍然适用,但是这次线程代码是由用户(js代码编写器)在javascript中创建的。

效率(而不是并发性)的角度来看,唯一要考虑的是产生的线程数。见下文:

关于线程安全性

Worker接口产生真正的OS级线程,并且如果您不小心,那么谨慎的程序员可能会担心并发会在您的代码中引起“有趣的”结果。

但是,由于Web工作人员已经仔细控制了与其他线程的通信点,因此实际上很难引起并发问题。无法访问非线程安全组件或DOM。而且,您必须通过序列化的对象将特定数据传入和传出线程。因此,您必须非常努力地在代码中引起问题。


聚苯乙烯

除了理论外,还应始终准备好接受的答案中描述的可能的极端情况和错误


7

JavaScript / ECMAScript旨在驻留在主机环境中。也就是说,除非宿主环境决定解析并执行给定脚本,并提供使JavaScript实际上有用的环境对象(例如浏览器中的DOM),否则JavaScript实际上不会做任何事情

我认为给定的功能或脚本块将逐行执行,这对于JavaScript是有保证的。但是,主机环境可能可以同时执行多个脚本。或者,主机环境可以始终提供提供多线程的对象。setTimeoutsetInterval是一个例子,或主机环境的至少伪例子,提供一种方式做一些并发性(即使它不完全并发)。


7

实际上,父窗口可以与运行自己的执行线程的子窗口或同级窗口或框架进行通信。


6

@Bobince提供了一个非常不透明的答案。

摆脱了MárÖrlygsson的回答,由于以下简单事实,Javascript始终是单线程的:Javascript中的所有内容都在单个时间轴上执行。

那是单线程编程语言的严格定义。


4

没有。

我要反对这里的人群,但要忍受我。单个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 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 2012年

1
我必须同意答案,只是因为当前的ECMAScript 没有为并发ECMAScript执行上下文提供任何准备(尽管可以说对于C也是如此)。然后,像这个答案一样,我认为任何具有并发线程能够修改共享状态的实现都是ECMAScript 扩展
user2864740 2014年

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

但是,如果您尝试在Windows上的Opera或Firefox稳定版中运行此程序,并在屏幕上弹出警告窗口的情况下最小化/最大化窗口,则将出现以下内容:

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

我不想说这是“多线程”,但是某些代码段在错误的时间内执行了,而我对此并不期望,现在我的状态已损坏。更好地了解这种行为。


-4

尝试将两个setTimeout函数相互嵌套,它们将表现为多线程(即,外部计时器在执行其功能之前不会等待内部的计时器完成)。


5
chrome正确地执行了此操作,dunno在@James看到它是多线程的情况下... :(setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)记录,哟dawg应该总是先出现,然后是控制台日志输出)
Tor Valamo 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.