JS 中的多线程

2025年4月18日 | 阅读 7 分钟

引言

网站设计已在许多科技公司中实现,其中 JavaScript 编程语言变得高效。尽管它具有多样性,但其固有的单线程特性是其基本品质之一。与 C++ 或 Java 等语言不同,这些语言的多线程内置于语言结构中,JavaScript 在单线程环境中运行代码。但随着网络技术的发展以及对更有效、非阻塞、并行处理的需求增长,JavaScript 已经开发出管理此类活动的能力。本文将讨论 JavaScript 的线程概念,它如何处理并发(尽管它是单线程的),以及在 JavaScript 中实现并行处理的不同工具和方法。

JS 的单线程特性

它采用单线程架构运行。这意味着一次只执行一个任务,并且所有代码都在一个线程上执行。另一方面,在多线程系统中,任务可以分解为多个线程,甚至可以并发执行。因为 JavaScript 用于网页浏览器,其中顺序任务执行对于防止不一致的网页渲染是必需的,所以它是单线程的。

事件循环和异步编程

由于事件循环异步编程,JavaScript 包含了同时处理多个任务的技术,即使它只在单个线程中运行。事件循环是使 JavaScript 即使是单线程也能实现非阻塞的主要组件之一。当一个长时间运行的任务运行时,它可能会阻止其他代码的运行。另一方面,JavaScript 通过在异步编程中利用 async/await、promise 和回调来避免这种情况。

事件循环的工作原理

调用栈:它控制函数执行的后进先出 (LIFO) 序列。调用函数时,它会被推入调用栈。完成后,它会从栈中弹出。与此同时,JavaScript 的事件循环通过排队回调来管理异步操作。当调用栈为空时,事件循环处理这些回调,确保非阻塞执行。由于其设计,JavaScript 即使是单线程也能处理并发,允许异步操作顺利运行。

任务队列:它对于其非阻塞、单线程事件循环架构至关重要。JavaScript 使用事件循环有效地管理异步进程,同时按顺序处理任务。一旦任务完成,例如 setTimeout 或网络请求,它就会进入任务队列。事件循环会持续检查调用栈是否为空,并从任务队列中选择下一个任务并执行。尽管 JavaScript 是单线程的,但此方法确保了非阻塞执行,使其能够轻松处理许多操作。

事件循环:它使用事件循环来管理非阻塞任务,如计时器、网络请求和 I/O 操作,但它只在单个线程中运行代码。事件循环不断扫描调用栈,以查找需要完成的任务。当回调完成或准备好时,它会从回调队列推送到调用栈。JavaScript 现在可以同时处理多个任务,而不会锁定主线程,从而确保响应性和流畅的性能。

异步程序示例

在这种情况下,尽管 setTimeout 导致延迟,主线程仍继续运行其他代码。这说明了 JavaScript 即使是单线程,也可以通过利用事件循环来管理异步任务。

Web Workers:在 JS 中引入多线程

凭借 Web Workers 和异步编程等功能,当代 JavaScript 环境(如 Web 浏览器和 Node.js)通过多线程实现并发。Web Workers 提高了文件处理和繁重计算等操作的性能,它允许建立辅助线程来执行复杂的计算,而不会停止主线程。JavaScript 的多任务处理能力通过 promise 和 async/await 等异步特性得到增强,这些特性允许非阻塞操作,但无法达到像为多线程设计的语言那样的真正并行性。

什么是 Web Workers?

它们允许 JavaScript 代码在与主线程分离的另一个线程上以静默模式运行。通过管理可能导致主 UI 线程挂起的任务、搜索大量数据集以及执行复杂计算,您可以改善用户界面和整体速度。

但是,Web Workers 确实有一些限制

  • 它们无法直接修改网页,因为它们无法访问 DOM
  • 消息用于 Web Workers 和主线程之间的通信,这可能导致大量或频繁数据交换的开销。

示例

工作线程在单独的线程上计算大数字的总和。在进行此计算时,主线程可以继续执行其他任务,例如绘制 UI 元素。计算完成后,工作线程使用 postMessage 将结果返回给主线程。

Web Workers 的优势

性能提升

它们可以在后台执行 CPU 密集型操作,例如复杂的计算或数据处理。这可以防止管理用户交互、渲染和其他 UI 功能的主线程被阻塞。结果,网页保持响应,从而改善用户体验。

响应性 (Responsiveness)

通过将繁重的工作外包给 Web Workers,主线程可以自由地专注于用户输入,例如滚动、点击和打字,而不会出现延迟。这对于当前的网络应用程序至关重要,因为即使软件正在管理大量数据或密集操作,用户也希望获得无缝连接。

并行执行

它们通过并行执行任务来高效利用多个 CPU 内核。JavaScript 在单线程环境中一次只能处理一个任务,而 Web Workers 允许同时运行多个任务,从而提高整体速度。

大型任务的可扩展性

在使用大型数据集、图像处理或视频编码时,它们特别有用。如果这些任务并发执行,主线程可能会显著减慢。可以使用 Web Workers 将此类大型活动分解为多个工作线程,从而确保高效完成。

Web 应用程序灵活性

它们促进了适应性强的 Web 应用程序的开发,这些应用程序可以管理并发网络请求、文件读取和加密。开发人员可以通过将这些操作转移到后台线程来创建具有更多功能和复杂性而又不影响性能的应用程序。

增强用户体验

它们大大降低了“脚本无响应”错误的风险,当主线程长时间停止时会发生此错误。这确保了即使进行密集计算或长时间任务,应用程序也将保持流畅和运行。

Web Workers 的限制

  • 无 DOM 访问:它们无法直接修改 DOM。所有 DOM 相关任务都必须由主线程执行。
  • 消息开销:消息用于工作线程和主线程之间的通信。这会导致延迟,尤其是在交换大量数据时。
  • 线程限制:可以建立的最大 Web Workers 数量因 Web 浏览器而异。过度使用 Web Workers 会导致性能下降。

共享数组缓冲区

与标准数组缓冲区类似,共享数组缓冲区允许许多线程从同一内存中读取和写入数据。这消除了线程之间的数据复制需求,并允许真正的并行计算。

原子操作

当多个线程访问共享内存时,会发生竞态条件,并且计算结果取决于线程执行的随机时序。它为用户提供了 Atomics 对象,该对象提供原子任务,例如减法、加法和可比性,以确保存储操作在进程之间正确执行。

示例

实现 JavaScript 并发的其他工具

Node.js 和 Cluster 模块

它在服务器端 JavaScript 中使用基于事件的独立架构来处理并发。尽管 Node.js 仅在单线程中工作,但它通过使用延迟 I/O 快速处理众多查询。它为 CPU 密集型活动提供了 Cluster 模块,允许您构建共享服务器端口的多个进程。

Parallel.js

它是一个 JavaScript 库,旨在简化 JavaScript 并行计算。它允许您使用在线工作线程并发执行任务,同时消除物理设置和处理工作线程所带来的一部分困难。

结论

尽管 JavaScript 在设计上是一种单线程语言,但它包含了强大的并发和并行处理管理功能。事件循环有效地管理异步任务,从而使主线程永远不会阻塞。对于需要真正并行性的更复杂用例,Web Workers、SharedArrayBuffer 和 Atomics 为并发任务执行提供了强大的功能。

通过利用 Node.js、Cluster 和 Parallel.js 等框架和工具,设计人员可以利用多核处理器的功能来执行 CPU 密集型活动。JavaScript 开发人员需要了解这些方法以及何时应用它们来开发适应性强、性能卓越的应用程序。随着 JavaScript 的进一步发展,我们可以期待更强大的并发和并行处理技术,扩展这种灵活语言的能力。