关于并发性:JavaScript是否保证是单线程的?

Is JavaScript guaranteed to be single-threaded?

众所周知,Javascript在所有现代浏览器实现中都是单线程的,但它是在任何标准中指定的,还是只是传统的?假设javascript总是单线程的,这是完全安全的吗?


这是个好问题。我想说"是的"。我不能。好的。

javascript通常被认为只有一个脚本(*)可见的执行线程,因此当您输入内联脚本、事件侦听器或超时时,您将完全处于控制状态,直到您从块或函数的末尾返回。好的。

(*:忽略浏览器是否真的使用一个OS线程实现其JS引擎,或者是否由WebWorkers引入其他有限的执行线程的问题。)好的。

然而,事实上,这并不完全是真的,以卑鄙的方式。好的。

最常见的情况是即时事件。当您的代码执行某些操作导致这些错误时,浏览器将立即触发这些错误:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
    l.value+= 'blur
'
;
};
setTimeout(function() {
    l.value+= 'log in
'
;
    l.focus();
    l.value+= 'log out
'
;
}, 100);
i.focus();
1
2
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

好的。

结果在EDOCX1中(0),除ie外,这些事件不仅因为直接调用focus()而触发,还可能因为调用alert(),或打开弹出窗口,或其他移动焦点的事件而发生。好的。

这也可能导致其他事件。例如,在focus()调用unfocus之前添加一个i.onchange侦听器并在输入中键入一些内容,并且日志顺序是log in, change, blur, log out,除非在opera中它是log in, blur, log out, change和ie中它是log in, change, log out, blur。好的。

类似地,在一个元素上调用click(),该元素提供在所有浏览器中立即调用onclick处理程序(至少这是一致的!).好的。

(我在这里使用的是Direct on...事件处理程序属性,但在addEventListenerattachEvent中也会发生这种情况。)好的。

还有一系列的情况,在这种情况下,尽管您没有采取任何措施来激发代码,但是在您的代码被线程插入时,事件可以触发。一个例子:好的。

1
2
3
4
5
6
7
8
9
10
11
12
var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
    l.value+= 'alert in
'
;
    alert('alert!');
    l.value+= 'alert out
'
;
};
window.onresize= function() {
    l.value+= 'resize
'
;
};
1
2
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

好的。

点击alert,你会得到一个模式对话框。在你取消对话之前,不会再执行脚本了,是吗?不。调整主窗口的大小,您将在文本区域中得到alert in, resize, alert out。好的。

当模式对话框打开时,您可能认为不可能调整窗口的大小,但事实并非如此:在Linux中,您可以根据自己的喜好调整窗口的大小;在Windows中,这并不容易,但您可以通过将屏幕分辨率从窗口不适合的大屏幕改为小屏幕来实现,从而调整窗口的大小。好的。

你可能会认为,只有EDOCX1(可能还有一些更像scroll)可以在用户没有与浏览器进行活动交互时触发,因为脚本是线程化的。对于单扇窗户,你可能是对的。但是,一旦你做了跨窗口脚本,一切都会变得一团糟。对于除Safari之外的所有浏览器,当其中任何一个浏览器繁忙时,都会阻止所有窗口/选项卡/框架,您可以从另一个文档的代码与文档交互,在单独的执行线程中运行,并导致任何相关的事件处理程序被触发。好的。

在脚本仍处于线程状态时,可以引发可导致生成的事件的位置:好的。

  • popups(alertwhen the情态,confirmprompt)是开放的,但在browsers歌剧;>

  • 恩,在showModalDialogbrowsers在线支持;>

  • "this page on the脚本may be busy……"对话框,choose to let the如果你是连续运行脚本,allows events to resize类火灾和模糊甚至好handled脚本在中间whilst the is of a busy环,除了在歌剧院。>

  • 当针在我身边,太阳在IE插件和Java Applet的,可以调用任何方法安在线events to allow to be王火和脚本进行。我永远不会在这个敏感的错误,可能有一个固定的太阳(certainly希望恩因为我知道)。>

  • probably黑莓。?我因为测试的复杂性和browsers since have this海外。>

在总结appears to most users,JavaScript,most of the time,to have a严格执行单线程事件驱动的杂志。在这样的现实,它有没有事。EN is not of this is how much清楚我有多么简单的设计错误和蓄意的,但如果你的写作especially复合应用,跨框架脚本窗口/酮,there is every EN能咬你的机会吗?-?在硬盘intermittent &,-调试方式。>

最糟糕的是if the to the最坏的模式,你可以在解决并发问题的间接事件的反应。当安事件中来,它没有落在with the queue中阶与新政后,在setInterval函数。如果你打算在你的写作框架to be used by做复合应用,这是好的行动。postMessagePain of the will also hopefully soothe跨脚本文件在未来。>

好吧。


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

此外,HTML5已经指定了Web工作者(一个用于多线程javascript代码的显式、标准化的API),将多线程引入基本的javascript中,这一事实基本上是毫无意义的。

(请注意:尽管setTimeout/setInterval、http-request-onload-even t s(xhr)和ui-events(click、focus等)提供了一个多线程的粗略印象-它们仍然是沿着一个时间轴执行的-一次一个-因此,即使我们事先不知道它们的执行顺序,也无需担心外部条件在执行事件处理程序、定时函数或XHR回调期间更改的操作。)


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


是的,尽管InternetExplorer9将在一个单独的线程上编译JavaScript,以准备在主线程上执行。不过,作为一名程序员,这并没有改变任何东西。


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


javascript/ecmascript被设计为生活在主机环境中。也就是说,除非宿主环境决定解析和执行给定的脚本,并提供让JavaScript真正有用的环境对象(例如浏览器中的DOM),否则JavaScript实际上不做任何事情。

我认为给定的函数或脚本块将逐行执行,这对于JavaScript是有保证的。然而,也许主机环境可以同时执行多个脚本。或者,主机环境总是可以提供一个提供多线程的对象。setTimeoutsetInterval是主机环境的示例,或者至少是伪示例,它们提供了一种执行某些并发的方法(即使它不是完全并发的)。


我想说的是,规范并没有阻止某人创建一个在多个线程上运行javascript的引擎,它要求代码执行访问共享对象状态的同步。

我认为单线程非阻塞范式是因为需要在浏览器中运行javascript,而用户界面不应该在浏览器中阻塞。

nodejs遵循浏览器的方法。

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

..."Rhino guarantees that accesses to properties of JavaScript objects are atomic across threads, but doesn't make any more guarantees for scripts executing in the same scope at the same time.If two scripts use the same scope simultaneously, the scripts are responsible for coordinating any accesses to shared variables."

通过阅读Rhino文档,我得出这样的结论:有人可以编写一个JavaScript API,该API也会生成新的JavaScript线程,但该API是Rhino特有的(例如,节点只能生成新的进程)。

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

了解浏览器和节点的方式如下:

  • 所有的JS代码都是在一个线程中执行的吗?是的。

  • JS代码能导致其他线程运行吗?是的。

  • 这些线程能否改变JS执行上下文?:没有,但他们可以(直接/间接(?)追加到事件队列。

因此,对于浏览器和nodejs(可能还有很多其他引擎),javascript不是多线程的,而是引擎本身。


@波宾斯提供了一个非常不透明的答案。

抢劫了M_r?Rlygsson的答案是,由于这个简单的事实,javascript总是单线程的:javascript中的所有内容都是沿着单个时间线执行的。

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


不。

我要反对这里的人群,但请忍受。单个JS脚本旨在有效地实现单线程,但这并不意味着它不能被不同地解释。

假设您有以下代码…

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

这样写的目的是期望到循环结束时,列表中必须有10000个条目,这些条目是索引的平方,但是VM可能会注意到循环的每次迭代都不会影响到另一个,并且使用两个线程重新解释。

第一线程

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

第二线程

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

我在这里简化,因为JS数组比愚蠢的内存块更复杂,但是如果这两个脚本能够以线程安全的方式向数组添加条目,那么在两个脚本都执行完之后,它将得到与单线程版本相同的结果。

虽然我不知道有任何虚拟机检测到这样的可并行代码,但对于JIT虚拟机来说,它很有可能在将来出现,因为它在某些情况下可以提供更高的速度。

进一步考虑到这个概念,代码可能会被注释,以让VM知道如何转换为多线程代码。

1
2
3
4
5
6
7
8
9
10
11
// 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是单线程的传统。


嗯,chrome是多进程的,我认为每个进程都处理自己的javascript代码,但据代码所知,它是"单线程的"。

Javascript中不支持多线程,至少不显式支持,因此没有什么区别。


我尝试过@bobince的例子,只做了一点修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<html>
<head>
    Test
</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
'
;

            s = 10;
            let s2 = s;

            alert('alert!');

            s = s + s2;

            l.value += 'click end
'
;
            l.value += `result = ${s}, should be ${s2 + s2}
`;
            l.value += '----------
'
;
        });

        window.addEventListener('resize', function() {
            if (s === 10) {
                s = 5;
            }

            l.value+= 'resize
'
;
        });
   
</body>
</html>

因此,当您按下运行,关闭警报弹出窗口并执行"单线程"时,您应该看到如下内容:

1
2
3
click begin
click end
result = 20, should be 20

但是,如果您尝试在Opera或firefox中在Windows上稳定运行它,并在屏幕上弹出警报,最小化/最大化窗口,那么会出现如下情况:

1
2
3
4
click begin
resize
click end
result = 15, should be 20

我不想说,这是"多线程处理",但一些代码在错误的时间执行,我没有预料到这一点,现在我有一个损坏的状态。更好地了解这种行为。


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