JavaScript保证是单线程的吗?

时间:2010-04-29 00:24:54

标签: javascript concurrency

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

12 个答案:

答案 0 :(得分:544)

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

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">

除了IE之外的所有结果log in, blur, log out。这些事件不仅仅是因为您直接调用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...事件处理程序属性,但addEventListenerattachEvent也是如此。)

还有一些情况可以在代码被线程化时触发事件,尽管你已经完成了 nothing 来激活它。一个例子:

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,您将获得一个模态对话框。在您解除对话之前不再执行脚本,是吗?不。调整主窗口的大小,您将在textarea中获得alert in, resize, alert out

您可能认为在模式对话框启动时调整窗口大小是不可能的,但不是这样:在Linux中,您可以根据需要调整窗口大小;在Windows上它并不是那么容易,但是你可以通过将屏幕分辨率从较大的屏幕分辨率更改为较小的屏幕分辨率(窗口不适合)来实现,从而使其调整大小。

您可能会认为,当用户没有与浏览器进行活动交互时,只有resize(可能还有一些更像scroll)可以触发,因为脚本是线程化的。对于单个窗口,您可能是对的。但是,只要你在进行跨窗口脚本编写,这一切都会进入底池。对于Safari之外的所有浏览器,当其中任何一个窗口/标签/框架忙时,它们会阻止所有窗口/标签/框架,您可以从另一个文档的代码中与文档交互,在单独的执行线程中运行并导致任何相关的事件处理程序火。

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

  • 当模态弹出窗口(alertconfirmprompt)打开时,除了Opera之外的所有浏览器;

  • showModalDialog期间支持它的浏览器;

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

  • 前一段时间,对于我,在使用Sun Java插件的IE中,调用applet上的任何方法都可以允许触发事件并重新输入脚本。这总是一个对时间敏感的错误,而Sun可能已经修复了它(我当然希望如此)。

  • 可能更多。自从我测试了这个版本已经有一段时间了,浏览器从那时起就变得复杂了。

总之,大多数用户在大多数情况下都会看到JavaScript拥有严格的事件驱动的单个执行线程。实际上,它没有这样的东西。目前尚不清楚这有多少只是一个错误和多少刻意的设计,但如果你正在编写复杂的应用程序,特别是跨窗口/框架脚本的应用程序,它有可能咬你 - 并且间歇性地,难以调试的方式。

如果最糟糕的情况发生,您可以通过间接所有事件响应来解决并发问题。当一个事件进入时,将其放入队列并稍后在setInterval函数中按顺序处理队列。如果您正在编写一个您打算被复杂应用程序使用的框架,那么这样做可能是一个很好的举措。 postMessage也有望在未来缓解跨文档脚本的痛苦。

答案 1 :(得分:110)

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

除此之外,HTML5 already specifies Web Workers(一种用于多线程javascript代码的显式,标准化API)将多线程引入基本Javascript的事实将毫无意义。

注意其他评论者:即使setTimeout/setInterval,HTTP请求onload事件(XHR)和UI事件(点击,焦点等)也提供了多重的印象-threadedness - 它们仍然沿着一个时间轴执行 - 一次一个 - 所以即使我们事先不知道它们的执行顺序,也不必担心在执行事件处理程序,定时函数期间外部条件发生变化或者XHR回调。)

答案 2 :(得分:16)

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

答案 3 :(得分:10)

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

答案 4 :(得分:7)

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

我认为给定的函数或脚本块将逐行执行,并且保证JavaScript。但是,主机环境可能同时执行多个脚本。或者,主机环境始终可以提供提供多线程的对象。 setTimeoutsetInterval是主机环境的示例,或者至少是伪示例,提供了一种执行并发的方法(即使它并不完全是并发)。

答案 5 :(得分:7)

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

答案 6 :(得分:5)

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

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

Nodejs遵循浏览器的方法。

Rhino 引擎然而,支持在不同的线程中运行js代码。执行不能共享上下文,但它们可以共享范围。 对于这种特定情况,文档说明:

  

...“Rhino保证对JavaScript对象属性的访问在线程中是原子的,但不会对同时在同一范围内执行的脚本提供更多保证。如果两个脚本同时使用相同的范围, 脚本负责协调对共享变量的任何访问。“

从阅读Rhino文档我得出结论,有人可以编写一个javascript api,它也会产生新的javascript线程,但api将是特定于rhino的(例如节点只能生成一个新进程)。

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

以我看到的方式来构思浏览器和nodejs:

  • 是否所有js代码都在一个线程中执行? :是的。

  • js代码可以导致其他线程运行吗? :是的。

  • 这些线程是否可以改变js执行上下文?:否。但是它们可以(直接/间接(?))附加到事件队列。

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

答案 7 :(得分: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数组比内存中的dumb块更复杂,但是如果这两个脚本能够以线程安全的方式向数组中添加条目,那么当它们都执行它时将与单线程版本具有相同的结果。

虽然我不知道任何虚拟机检测到这样的可并行化代码,但它似乎可能在未来为JIT虚拟机而存在,因为它可以在某些情况下提供更快的速度。

进一步考虑这个概念,可以注释代码以让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是传统的单线程。

答案 8 :(得分:4)

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

重复MárÖrlygsson的回答,Javascript总是单线程的,因为这个简单的事实:Javascript中的所有内容都是在一个时间轴上执行的。

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

答案 9 :(得分:3)

嗯,Chrome是多进程的,我认为每个进程都处理自己的Javascript代码,但就代码所知,它是“单线程”。

Javascript中没有任何支持多线程,至少没有明确说明,所以它没有什么区别。

答案 10 :(得分: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

但是如果您尝试在Windows上运行Opera或Firefox,并在屏幕上显示警告弹出窗口最小化/最大化窗口,那么将会出现以下情况:

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

我不想说,这是“多线程”,但是某些代码在错误的时间内执行而我没有预料到这一点,现在我的状态已经损坏了。 最好知道这种行为。

答案 11 :(得分:-4)

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