Javascript如何单线程?

时间:2014-02-12 04:37:11

标签: javascript multithreading

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

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

此代码的结果为1 3 4 2。如您所见,4位于2之后,这让我想知道在单线程环境中2之后不应该4?如果没有,那么为什么JS知道第二个setTimeout应该在第一个之前完成?是不是应该有两个线程同时工作以完成两个setTimeout以通知EventLoop

6 个答案:

答案 0 :(得分:26)

JavaScript(在浏览器中)不会并发运行 2

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

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

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


1 或者它可能不会;这可以在select/poll实现中没有任何线程的情况下处理。

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

答案 1 :(得分:11)

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

答案 2 :(得分:10)

Javascript使用名为 Eventloop 的内容进行异步调用。 setTimeout被推送到EventLoop,因为它是一个回调。并且主线程继续执行。一旦主要完成,然后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 :(得分:5)

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

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

现在,浏览器可以从操作系统访问多个线程(虽然只为JS执行提供单个线程环境)。这些setTimeout由场景后面的另一个线程处理。

飞利浦罗伯特的视频精美地解释了导致单线程javascript“异步”的队列和事件循环的概念。

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

答案 4 :(得分:0)

以下是步骤:

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

  2. 执行它。 (在控制台中打印1)-时间(〜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)-时间(〜0)

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

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

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

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

答案 5 :(得分:0)

定义-多线程: 多线程是一种执行模型,它允许多个线程存在于进程的上下文中,以便它们独立执行但共享进程资源。 从这个意义上讲,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基本相同,具有原子基元。