JavaScript中的多线程或异步代码是如何工作的?

3
我在JavaScript方面不是新手。实际上我已经在此领域工作了三到四个月,但今天我读到了"什么是JavaScript?"的声明:
JavaScript是单线程、非阻塞、异步、并发的语言。
这使我感到困惑。如果JavaScript是单线程的,它怎么可能是并发的,它怎么可能是异步的,因为你需要跟踪你的异步代码正在做什么,并且没有另一个线程,这是不可能同时跟踪2个或以上的代码的。

2
你正在考虑并行执行,这与并发执行不同:https://dev59.com/gXI-5IYBdhLWcg3wW2-p - Sverri M. Olsen
1
“那么它如何能够是异步的呢?因为您需要跟踪异步代码的执行,而不需要另一个线程。”简单的解释是:语言的用户界面是单线程的,但是宿主运行时/环境是多线程的。浏览器有线程池来执行常规后台任务,Node.js也有一个。” - zerkms
3个回答

8

啊..这里有一点需要说明:

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

当它在等待网络加载、等待磁盘读取或等待操作系统返回数据时,它可以运行其他代码。

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

当它在等待超时返回执行该代码时,它可以运行来自其他超时、网络调用或系统中的任何其他异步代码的代码。但是,它每次只运行一个代码块,这就是为什么我们说它是单线程的原因。
那个线程可能会反弹很多次。
正如其他人所说,有Web Workers和Service Workers,但这些与您的主线程非常隔离。它们不能在您的主线程背后更改值。
根据评论更新
事件循环的工作方式是:
等待事件
处理该事件。
当处理事件时,JavaScript确实被阻塞。在代码运行时,该页面中的任何其他内容(假设为浏览器主线程)都无法运行。
就JS而言,它不是像C或C++中那样字面上的“事件循环”。它只是等待事件发生。
/// 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的。 - Ajay Gaur
@AjayGaur 添加了更多细节。 - Jeremy J Starcher

2

最新更新

如果您了解Webassembly,则目前已有一个提议,通过本地编译模块实现线程

pthreads风格,请阅读此git问题跟踪器链接(1073)


续上@Jeremy J Starcher的回答。

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

要了解更多有关JS中事件循环执行的信息,我强烈推荐您观看这个Youtube视频。Philip Roberts的解释非常好。

在过去,开发人员会费尽周折地使用以下方法来实现类似于线程模型:

  • 0毫秒的setTimeout或setInterval:基本上指示引擎在HTTP请求期间进入空闲或等待模式时承担重要任务,或者通过轮流切换来执行代码。
  • 隐藏的Iframe:在沙盒中运行JS代码,并通过桥接从父窗口通信到iframe及其反向。从技术上讲,Iframe并不在单独的线程上运行,但可以作为一个伪线程来完成工作。

快进到ECMA的多线程模型:
最近,随着在JS引擎中生成线程以卸载一些较小的逻辑任务或网络代理任务到单独的线程并集中于主线程上的UI驱动任务(如呈现和交互层),事情发生了变化,这是有意义的。
考虑到这个要求,ECMA提出了两种基本的模型/API来解决这个问题。
1. Web Worker(SIC - Mozilla)
Web Workers使得在与Web应用程序的主执行线程分离的后台线程中运行脚本操作成为可能。这样做的好处是可以在单独的线程中执行繁重的处理,允许主线程(通常是UI)在没有被阻塞/减慢的情况下运行。
[ WebWorker可以分为两部分 ]
- 共享工作者 SharedWorker接口表示一种特定类型的工作线程,可以从多个浏览上下文中访问,例如多个窗口、iframes甚至工作线程。它们实现了与专用工作线程不同的接口,并具有不同的全局范围SharedWorkerGlobalScope
专用工作线程:与Webworker相同,使用 Worker() API创建,但使用 DedicatedWorkerGlobalScope
Worker是使用构造函数(例如Worker())创建的对象,运行命名的JavaScript文件 - 该文件包含将在工作线程中运行的代码;工作线程在与当前窗口不同的另一个全局上下文中运行。在专用工作线程的情况下,此上下文由DedicatedWorkerGlobalScope对象表示。

2. Service Worker(SIC - Mozilla)

服务工作者本质上是充当代理服务器的角色,位于Web应用程序、浏览器和网络之间(如果可用)。它们旨在(除其他功能外)实现有效的离线体验,拦截网络请求并根据网络是否可用以及更新的资源是否驻留在服务器上采取适当的措施。它们还将允许访问推送通知和后台同步API。

一个例子是在PWA-渐进式Web应用程序中使用,用于下载脚本、懒加载资产等目的。


阅读HTML5Rocks上Eric Bidelman关于代码本身和实现的好解释的文章

0

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

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

当我们真正想表达的是我们的程序是这样还是那样时,我认为说一个语言是这样或那样是错误的。

例如:NodeJS是单线程的,并且可以异步运行代码,因为它使用事件驱动的行为。(某些事情出现并触发事件... Node处理它,如果它是像在线请求这样的东西,它会做其他事情而不是等待回应...当响应到达时,它会触发事件,Node捕获它并执行需要执行的操作)。

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


1
因为它采用了事件驱动的行为。--- 因为它实际上是使用线程池。Node.js在内部并不是单线程的。 - zerkms
3
整个答案实际上在技术上多少是不正确的。答案的主要问题在于混淆了语言和运行时/环境。 - zerkms

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接