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


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


我的答案是肯定的——因为如果浏览器的javascript引擎异步运行,几乎所有现有的(至少所有非平凡的)javascript代码都会崩溃。

再加上HTML5已经指定了Web Workers(用于多线程javascript代码的显式标准化API),在基本javascript中引入多线程是毫无意义的。

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


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

Javascript中不支持多线程,至少不是显式的,所以没有什么区别。


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


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

我认为给定的函数或脚本块将逐行执行,这是JavaScript的保证。然而,宿主环境可能同时执行多个脚本。或者,宿主环境可以始终提供一个提供多线程的对象。setTimeout和setInterval是一些例子,或者至少是伪例子,它们说明一个主机环境提供了一种实现并发性的方法(即使它不是真正的并发性)。


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

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

(*:忽略了浏览器是否真的使用一个操作系统线程实现他们的JS引擎,或者WebWorkers是否引入了其他有限的执行线程。)

然而,在现实中,这并不完全正确。

最常见的情况是即时事件。当你的代码做了一些导致它们的事情时,浏览器会立即触发它们:

var l= document.getElementById('log'); var i= document.getElementById('inp'); i.onblur= function() { l.value+= 'blur\n'; }; setTimeout(function() { l.value+= 'login in\n'; l.focus(); l.value+= '注销\n'; }, 100); i.focus(); <textarea id=“log” rows=“20” cols=“40”></textarea> <input id=“inp”>

结果登录,模糊,注销除了IE。这些事件并不仅仅因为您直接调用了focus()而触发,它们也可能因为您调用了alert(),或者打开了一个弹出窗口,或者其他任何移动焦点的操作而发生。

这也可能导致其他事件。例如,添加一个i.onchange监听器,在focus()调用取消聚焦之前在输入中输入一些东西,日志顺序是登录、更改、模糊、注销,除了在Opera中是登录、模糊、注销、更改,在IE中是(更难以解释的)登录、更改、注销、模糊。

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

(我用的是…事件处理程序属性,但同样发生在addEventListener和attachEvent。)

在许多情况下,尽管您没有做任何事情来触发事件,但当您的代码被线程插入时,事件可能会被触发。一个例子:

var l= document.getElementById('log'); document.getElementById('act').onclick= function() { l.value+= 'alert in\n'; 警报(“警报! l.value+= 'alert out\n'; }; window.onresize= function() { l.value+= 'resize\n'; }; <textarea id=“log” rows=“20” cols=“40”></textarea> <按钮 id=“行动”>警报</button>

点击alert,你会得到一个模态对话框。在你终止对话之前不会再执行脚本,对吗?没有。调整主窗口的大小,你会在文本区得到警报进入,调整大小,警报退出。

你可能认为,当一个模态对话框打开时,调整窗口大小是不可能的,但事实并非如此:在Linux中,你可以随心所欲地调整窗口大小;在Windows上,这并不容易,但你可以通过将屏幕分辨率从较大的窗口调整为较小的窗口来实现,从而调整窗口的大小。

You might think, well, it's only resize (and probably a few more like scroll) that can fire when the user doesn't have active interaction with the browser because script is threaded. And for single windows you might be right. But that all goes to pot as soon as you're doing cross-window scripting. For all browsers other than Safari, which blocks all windows/tabs/frames when any one of them is busy, you can interact with a document from the code of another document, running in a separate thread of execution and causing any related event handlers to fire.

在脚本仍然线程化的情况下,你可以引起事件生成的地方:

when the modal popups (alert, confirm, prompt) are open, in all browsers but Opera; during showModalDialog on browsers that support it; the “A script on this page may be busy...” dialogue box, even if you choose to let the script continue to run, allows events like resize and blur to fire and be handled even whilst the script is in the middle of a busy-loop, except in Opera. a while ago for me, in IE with the Sun Java Plugin, calling any method on an applet could allow events to fire and script to be re-entered. This was always a timing-sensitive bug, and it's possible Sun have fixed it since (I certainly hope so). probably more. It's been a while since I tested this and browsers have gained complexity since.

总之,对于大多数用户来说,JavaScript在大多数情况下都具有严格的事件驱动的单线程执行。在现实中,它没有这样的东西。目前还不清楚其中有多少是简单的错误,有多少是故意设计的,但如果您正在编写复杂的应用程序,特别是跨窗口/框架脚本的应用程序,那么它很可能会以间歇性的、难以调试的方式咬你一口。

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


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


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


No.

我要跟大家唱反调,但请大家耐心听我说。单个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 worker开始使用Javascript,不太可能……更丑陋的系统将会出现,但我认为可以肯定地说Javascript传统上是单线程的。


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

所以,当你按下运行,关闭警报弹出并执行“单线程”,你应该会看到如下内容:

click begin
click end
result = 20, should be 20

但如果你尝试在Opera或Firefox稳定的Windows上运行这个,并最小化/最大化屏幕上弹出的警告窗口,那么会有这样的东西:

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

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


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

我认为单线程非阻塞模式是出于在浏览器中运行javascript的需要,ui不应该阻塞。

Nodejs遵循了浏览器的方法。

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

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

通过阅读Rhino文档,我得出结论,对于某些人来说,编写一个javascript api也可以生成新的javascript线程,但api将是特定于Rhino的(例如,节点只能生成一个新进程)。

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

我认为浏览器和nodejs之间的关系是这样的:

所有的js代码都在一个线程中执行吗?:是的。 js代码可以导致其他线程运行吗?:是的。 这些线程会改变js的执行上下文吗?:没有。但是它们可以(直接/间接地(?))追加到事件队列中 侦听器可以改变执行上下文。但是不要被愚弄,侦听器再次在主线程上原子地运行。

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


关于网络工作者的更新:

网络工作者的存在进一步证明了javascript可以是多线程的,从某种意义上说,有人可以用javascript创建代码,这些代码将在单独的线程上运行。

然而,web工作者并不能解决那些可以共享执行上下文的传统线程的问题。上面的规则2和3仍然适用,但这次线程代码是由用户(js代码作者)在javascript中创建的。

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

关于线程安全:

The Worker interface spawns real OS-level threads, and mindful programmers may be concerned that concurrency can cause “interesting” effects in your code if you aren't careful. However, since web workers have carefully controlled communication points with other threads, it's actually very hard to cause concurrency problems. There's no access to non-threadsafe components or the DOM. And you have to pass specific data in and out of a thread through serialized objects. So you have to work really hard to cause problems in your code.


P.S.

除了理论之外,对可能出现的极端情况和公认答案中描述的错误要时刻做好准备


Javascript引擎必须是单线程,但Javascript运行时不需要是单线程。

Javascript引擎是什么?这是执行实际JS代码的解释器。引擎需要主机。它不能自己运行。主机是Javascript运行时。

例如,运行在Chrome浏览器中的V8引擎是单线程的。Chrome浏览器是一个运行时&它有其他进程/线程支持V8引擎。

你可以查看这篇文章,在那里有漂亮的解释。如果有帮助的话,别忘了回复并点赞:)