Javascript 是如何单线程的?

IT技术 javascript multithreading
2021-01-29 19:16:28

我有一个关于 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应该在第一个之前完成?不应该有两个线程同时工作来完成两个setTimeouts 以便通知EventLoop吗?

6个回答

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

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

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

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


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

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

我们都依赖于这种行为并观察它——但这是在哪里记录的?
2021-03-22 19:16:28

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并发模型和 Event Loop 中带回数据

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 等待 0.99999 秒......并写入 4

然后再等一会儿,然后写 2

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

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

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

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

Philips Robert 的一段视频精美地解释了导致单线程javascript“异步”的队列和事件循环的概念。

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

以下是步骤:

  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) time(~0)

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

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

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

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

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

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

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

我本可以把它分成 20 步,但为了便于理解,12 步就足够了。