【问题标题】:How is Javascript single threaded?Javascript是如何单线程的?
【发布时间】:2014-03-10 05:42:35
【问题描述】:

我对 Javascript 的单线程特性有疑问。

console.log("1");
setTimeout(function(){console.log("2");},3000);
console.log("3");
setTimeout(function(){console.log("4");},1000);

这段代码的结果是1 3 4 2。如您所见,42 之后出现,这让我想知道在单线程环境中2 不应该在4 之后出现吗?如果不是,那么 JS 怎么知道第二个 setTimeout 应该在第一个之前完成?难道不应该有两个线程同时工作来完成两个setTimeouts,以便通知EventLoop吗?

【问题讨论】:

  • setTimeout 只是安排一个任务。 "2" 被安排在未来的3000ms,并且不会阻止其他代码执行。
  • 如果它同时完成一件以上的事情,它将是多线程的,但是您的代码不会同时做一件以上的事情。
  • 如果代码被安排在同一时间运行,或者意外地在超时时执行了一些代码,会发生什么?在这种情况下它是如何决定执行顺序的?
  • 例如如果我(愚蠢地)添加 pausecomp(999); 怎么办?在这个例子的最后? (见sean.co.uk/a/webdesign/javascriptdelay.shtm
  • 多线程和异步是两个不同的东西。

标签: javascript multithreading


【解决方案1】:

JavaScript(在浏览器中)同时运行2

一次最多可以执行 一个 setTimeout 回调 - 因为存在 一个 JavaScript 执行上下文或“线程”。

但是,要运行的“下一个计划超时”始终是 run .. next。 “4”在“2”回调之前运行,因为它计划提前运行同一时间有效地安排了超时(没有任何操作被阻塞),但“2”的间隔要长得多。

底层实现可能使用线程1 - 但是在同一个全局上下文中的JavaScript不会并发运行并保证一致 和所有回调之间的 原子 行为。


1 也可能不是;这可以在 select/poll 实现中无需任何线程即可处理。

2 在相同的上下文中:即 Tab/Window、WebWorker、主机浏览器控件。例如,虽然 WebWorkers 是并发运行的,但它们在不同上下文中这样做,并遵循相同的异步模型(例如,计时器使用的模型)。

【讨论】:

  • 我们都依赖这种行为并观察它——但这在哪里记录?
【解决方案2】:

Javascript 使用称为 Eventloop 的东西来进行异步调用。 setTimeout 被推送到 EventLoop,因为它是一个回调。并且主线程继续执行。一旦 main 完成,然后 EventLoop 将数据推送到主堆栈。 例如:

console.log("1");
setTimeout(function(){console.log("2");},0);
console.log("3");
setTimeout(function(){console.log("4");},1000);

当超时为0时,代码的输出将是,

1 3 2 4

因为它首先执行 Main 的调用,然后从 Eventloop 中取回数据 Concurrency model and Event Loop

【讨论】:

    【解决方案3】:

    Javascript 依次执行每一行。

    所以你告诉js:

    • 写1:js writes 1
    • 等待 3 秒,然后写入 2:ok I'll wait 3 seconds...now what?
    • 写3:ok I'll write 3, by the way, the 3 seconds is not up.
    • 等待 1 秒,然后写入 4:ok I'll wait 1 second...

    然后 js 等待 .99999 秒 ... 并写入 4

    然后再等待一段时间并写入 2

    【讨论】:

    • “顺便说一句,3 秒还没到”具有误导性。即使计时器的时间到了,JavaScript 也不可能在那里执行某些东西,这正是因为 JS 是单线程的。 JS 线程既可以是代码内的,也可以是代码外的;虽然它在代码中,但它不能执行任何其他操作。只有当您的代码放弃 JS 引擎的命令(通过从最外层函数退出)时,才会发生任何超时。
    • 这是一个非常不令人满意的解释。 setTimeout 怎么样?它允许同时运行多个重叠的超时。依赖于外部源的事件回调(例如XMLHttpRequest 上的结果)必须能够随时启动,但仍然在同一线程上运行,这一事实又如何呢?没有冒犯,但这个答案感觉太笼统了。
    • @Shien setTimeout() 仍在单线程运行,因此不能保证它们在精确的毫秒内触发。为了帮助理解,请参阅:ejohn.org/blog/how-javascript-timers-work
    【解决方案4】:

    setTimeout 的第二个参数采用 最小 时间,之后回调函数(第一个参数)将被推送到事件循环中,事件循环只不过是回调函数的队列。此队列用于实际开始执行。

    一旦遇到第一个 setTimeout,函数就会被推送到外面的某个地方,并被告知在重新进入单线程世界之前等待 3 秒。第二个超时功能也是如此,但它只需要等待 1 秒。这个单线程世界的入口点是回调队列。 JS 引擎继续正常执行,就像 settimeout 执行完成一样。现在,一旦 1 秒到期,秒超时功能就会被推送到队列中并等待执行。如果调用堆栈在那个时间点是清除的,那么函数会进行处理(假设它是队列的第一个成员)并打印“4”。现在如果这个时间还没有过去 3 秒,那么第一次超时的功能还在外面某个地方等待。一旦 3 秒过去,回调函数进入队列,由于调用堆栈被清除,它执行并打印“2”。

    现在浏览器可以从操作系统访问多个线程(尽管只为 JS 执行提供了一个单线程环境)。这些 setTimeouts 由后台的另一个线程处理。

    Philips Robert 的一个视频很好地解释了队列和事件循环的概念,这些概念导致了单线程 javascript 的“异步”。

    https://www.youtube.com/watch?v=8aGhZQkoFbQ

    【讨论】:

      【解决方案5】:

      步骤如下:

      1. 将 console.log(1) 添加到 JS 调用堆栈。时间(~0)

      2. 执行它。 (在控制台中打印 1)- time(~0)

      3. 添加 setTimeout(function(){console.log("2");},3000);到调用堆栈。 - 时间(~0)

      4. 将其移至事件循环并启动计时器。 - 时间(3 秒)

      由于 setTimeout 是一个异步函数,它会移动到事件循环中。

      1. 将 console.log(3) 添加到 JS 调用堆栈。时间(~0)

      2. 执行它。 (在控制台中打印 3)时间(~0)

      3. 添加 setTimeout(function(){console.log("4");},1000);到调用堆栈。时间(~0)

      4. 将其移至事件循环并启动计时器。 - 时间(1 秒)

      5. 1 秒计时器结束,因此它将移回调用堆栈并执行。

      6. 调用堆栈执行它。 (在控制台中打印 4)- time(~0)

      7. 3 秒计时器结束,因此它将移回调用堆栈并执行。

      8. 调用堆栈执行它。 (在控制台中打印 2)- time(~0)

      现在我们所说的同步是JS调用堆栈,它一次只能执行一件事。

      我本可以将其设置为 20 步,但为了便于理解,12 步就足够了。

      【讨论】:

        【解决方案6】:

        定义 - 多线程: 多线程是一种执行模型,它允许多个线程存在于进程的上下文中,以便它们独立执行但共享它们的进程资源。 从这个意义上说,JavaScript 完全是多线程的东西:

           let Ar;
            Ar = []
        
            function multiapp(ar,w,n) {
                ar.clear;
                for (let i = 0; i < n; i++)
                    setTimeout(function(){ar.push(w)},i);
            }
        
            +function fn() {
                //Thread 1
                setTimeout(() => multiapp(Ar,1,10),100)
                //Thread 2
                setTimeout(() => multiapp(Ar,2,10),100)
            }()
        
            setTimeout(function(){console.log('Ar(1) = ', Ar.filter((i) => i == 1).length,'Ar(2) = ', Ar.filter((i) => i == 2).length,' Ar = ',Ar)},2000);
        //Ar(1) =  10 Ar(2) =  10  Ar =  [ 1, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 ]`
        

        显然,线程 1 独立于线程 2 启动并执行自己的堆栈。两个线程共享对同一个上下文对象实例 Ar 的访问权,并以未知的方式对其进行修改。行为与具有原子原语的 Java 完全相同。

        【讨论】:

          猜你喜欢
          • 2011-02-13
          • 2020-09-19
          • 2018-12-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-12
          相关资源
          最近更新 更多