【问题标题】:How does multi-threading or async code in JavaScript work?JavaScript 中的多线程或异步代码如何工作?
【发布时间】:2025-12-22 00:55:06
【问题描述】:

我不是 javascript 的初学者。在过去的 3-4 个月里,我实际上一直在研究这个问题,但今天我读到了这篇关于“什么是 JavaScript?”的声明

JavaScript 是单线程、非阻塞、异步、并发的语言。

我迷路了。如果 JavaScript 是单线程的,它怎么可能是并发的,又怎么可能是异步的,因为你需要跟踪你的异步代码在做什么,而没有另一个线程,就不可能同时跟踪 2 个或更多代码?

【问题讨论】:

  • 你在考虑并行执行,和并发执行不一样:*.com/questions/1897993/…
  • “它怎么可能是异步的,因为你需要跟踪你的异步代码在做什么并且没有另一个线程”——简单的解释是:语言的用户空间是单线程的,但是承载它的运行时/环境在后台是多线程的。浏览器有adpools来做日常后台工作,nodejs也有。

标签: javascript multithreading asynchronous


【解决方案1】:

啊..事情是这样的:

JavaScript 是单线程的,但它有很多空闲时间。

当它等待某些东西从网络中加载出来,或者它正在等待某些东西从磁盘上或者等待操作系统将某些东西交还给它时,它可以运行其他代码。

setTimeout(function() {
  // Do something later.
}, 1000);

在等待该超时以返回执行该代码时,它可以运行来自其他超时的代码、网络调用或系统中的任何其他异步代码。然而,它一次只运行一个代码块,这就是为什么我们说它是单线程的。

那个线程可以反弹。很多。

而且,正如其他人所说,有网络工作者和服务工作者,但它们与您的主线程非常隔离。他们不能在你的主线程背后改变值。

根据评论更新

事件循环的工作原理:

  • 等待事件
  • 处理该事件。

JavaScript 在处理 事件时确实被阻塞了。在代码运行时,该页面中的其他任何内容(假设浏览器主线程)都无法运行。

它不是像在 C 或 C++ 中那样的文字 event loop,就 JS 而言。这只是等待发生的事件。

/// Sample code
document.addEventListener("click", function() { /* Handle click */ });

window.addEventListener("load", function() { /* handle load */ });

在这种情况下,我们的代码中有两个事件监听器。 JS 引擎将编译,然后执行这两个语句。然后,出于所有意图,在等待某事发生的同时“睡觉”。 实际上,同一个线程可能会处理各种管理任务,例如绘制 HTML 页面、监听移动动作和发出各种事件,但这对于本次讨论无关紧要。

然后,一旦页面的其余部分被加载,浏览器将发出一个load 事件,该事件将被监听器捕获并运行更多代码。

然后它会回到空闲状态,直到有人点击文档,然后运行更多代码。

如果我们把代码改成这样:

document.addEventListener("click", function() {
  while(true);
});

然后当有人点击文档时,我们的线程将进入无限循环,并且该窗口中的所有浏览器活动都将停止。甚至可能冻结整个浏览器,具体取决于您正在运行的浏览器。

最终,浏览器将有机会终止该任务,以便您恢复系统。

【讨论】:

  • 但是事件循环是如何工作的。据我所知,事件循环允许该线程独立工作,事件循环是 WebAPI 的功能,而不是 javascript 的。
  • @AjayGaur 添加了更多细节
【解决方案2】:

最新更新

如果您知道Webassembly,则通过本机编译的模块提出了Threads 的建议

pthreads-style 阅读这个 git 问题跟踪器link(1073)


继续@Jeremy J Starcher回答。

Javascript 始终是使用异步、非阻塞和事件驱动的执行模型的单线程运行时。

要了解有关 JS 中事件循环执行的更多信息,我强烈建议您观看此内容 Youtube videoPhilip Roberts 的精彩解释。

过去的美好时光,开发人员会拐弯抹角来实现类似于线程模型的使用

  • setTimeout with 0 - milliseconds 或 setIntervals :基本上指示引擎在 http 请求期间引擎进入空闲或等待模式时执行重要任务,或者通过间隔来回切换来执行代码有点循环时装。
  • 隐藏的 iframe :在沙箱中运行 JS 代码,通过桥从父级到 iframe 进行通信,反之亦然。从技术上讲,Iframe 不会在单独的线程上运行,而是将事情作为假线程完成。

通过 ECMA 将 [ >>> ] 快速转发到多线程模型:

随着需要在 JS 引擎中生成线程以将少量较小的逻辑任务或网络代理任务卸载到单独的线程并专注于 UI 驱动的任务(如主线程上的表示和交互层),事情发生了变化,这使得感觉。

考虑到这一要求,ECMA 提出了两个模型/API 基本上可以解决这个问题。

1. Web Worker: (SIC - Mozilla)

Web Workers 可以在后台运行脚本操作 与 Web 应用程序的主执行线程分开的线程。 这样做的好处是可以在 一个单独的线程,允许主(通常是 UI)线程运行 不会被阻止/减速。

[ WebWorker 可以一分为二 ]

SharedWorker 接口代表一种特定类型的工作者, 可以从多个浏览上下文访问,例如几个 windows、iframe 甚至是 worker。他们实现了一个接口 不同于敬业的员工,具有不同的全球范围, SharedWorkerGlobalScope.

  • Dedicated Worker:与 Webworker 相同,使用 Worker() API 创建,但使用 DedicatedWorkerGlobalScope

Worker 是一个使用构造函数(例如 Worker())创建的对象,它 运行一个命名的 JavaScript 文件——这个文件包含将 在工作线程中运行;工人在另一个全球环境中运行, 与当前窗口不同。此上下文由 DedicatedWorkerGlobalScope 对象在专用工作者的情况下


2. Service Worker(SIC - Mozilla)

Service Worker 本质上充当位于 Web 之间的代理服务器 应用程序,以及浏览器和网络(如果可用)。他们是 旨在(除其他外)能够创建有效的 离线体验,拦截网络请求并采取 根据网络是否可用和 更新的资产驻留在服务器上。他们还将允许访问 推送通知和后台同步 API。

一个示例用法是在 PWA - Progressive web app 中下载脚本、延迟加载资产。


阅读 Eric Bidelman 撰写的 article 关于 HTML5Rocks 的关于代码本身和实现的很好解释

【讨论】:

    【解决方案3】:

    JavaScript 可能是“单线程”的(我不确定是否真的如此),但您可以使用/创建 webworkers 在主线程之外运行 javascript。

    因此您可以同时并行运行两段代码。

    当我们真正的意思是我们的程序是这样或那样时,我认为说一种语言是这样或那样是错误的。

    例如:NodeJS 是单线程的,并且可以异步运行代码,因为它使用事件驱动的行为。 (出现一些事情并触发一个事件...... Node 处理它,如果它是一个在线请求,它会做其他事情而不是等待响应......当响应到来时,它会触发一个事件并且 Node 捕获它并做任何需要做的事情)。

    所以 Javascript 是...
    单线程?不,因为您可以将 WebWorkers 用作第二个线程
    非阻塞?您可以编写阻塞主线程的代码。只需构建一个执行一亿次的 for 或不使用回调。
    异步?不,除非您使用回调。
    并发?是的,如果您使用网络工作者、回调或承诺(实际上是回调)。

    【讨论】:

    • “因为它使用事件驱动的行为。” --- 因为它实际上使用了线程池。引擎盖下的 nodejs 不是单线程的。
    • 实际上整个答案在技术上或多或少是不正确的。答案的主要问题是它混淆了语言和运行时/环境。