首页 文章

JavaScript保证是单线程的吗?

提问于
浏览
555

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

12 回答

  • 1

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

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

  • 16

    没有 .

    我要在这里与人群对抗,但要忍受我 . 单个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块更复杂,但是如果这两个脚本能够以线程安全的方式向数组中添加条目,那么当两个脚本都完成执行时它将具有与单线程版本相同的结果 .

    虽然我不知道任何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是传统的单线程是安全的 .

  • 5

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

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

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

  • 105

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

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

    Note to others commenters: 尽管 setTimeout/setInterval ,HTTP请求onload事件(XHR)和UI事件(点击,焦点等)提供了多线程的粗糙印象 - 它们仍然沿着单个时间线执行 - 一次一个 - 所以,即使我们不需要担心在执行事件处理程序,定时函数或XHR回调期间外部条件发生变化 . )

  • 4

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

  • 10

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

    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() ,或者打开了一个弹出窗口或任何其他移动焦点的事件而发生 .

    这也可能导致其他事件 . 例如,在 focus() 调用解除它之前添加 i.onchange 侦听器并在输入中键入内容,并且日志顺序为 log in, change, blur, log out ,除了在Opera中 log in, blur, log out, change 和IE在哪里(甚至不太明显) log in, change, log out, blur .

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

    (我正在使用这里直接 on... 事件处理程序属性,但 addEventListenerattachEvent 也是如此 . )

    尽管你没有采取任何行动来激发事件,但是在你的代码被线程化的情况下,事件可能会发生一系列情况 . 一个例子:

    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之外的所有浏览器;

    _199_在 showModalDialog 期间支持它的浏览器;

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

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

    • 可能更多 . 自从我测试了这个版本已经有一段时间了,浏览器从此开始变得复杂 .

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

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

  • 7

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

  • 7

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

    我认为单线程非阻塞范例来自于在ui永远不应该阻塞的浏览器中运行javascript的需要 .

    Nodejs遵循浏览器的方法 .

    然而, Rhino 引擎, supports running js code in different threads . 执行不能共享上下文,但它们可以共享范围 . 对于这个特定情况,文档说明:

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

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

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

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

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

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

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

    因此,在浏览器和nodejs(可能还有很多其他引擎)的情况下,javascript不是多线程的,但引擎本身就是 .

  • 3

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

  • -4

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

  • 530

    好吧,Chrome是多进程的,我认为每个进程都处理自己的Javascript代码,但就代码而言,它是“单线程” .

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

  • 4

    我尝试了@ bobince的例子略有修改:

    <html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <textarea id="log" rows="20" cols="40"></textarea>
        
    <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
    

    我不想说,这是“多线程”,但是某些代码在错误的时间内执行,我不期待这个,现在我的状态已经损坏了 . 更好地了解这种行为 .

相关问题