Web Workers

在浏览器和服务器上,JavaScript程序在单个处理线程上运行。这意味着程序一次只能做一件事。简单来说,你的新电脑可能有32核的CPU,但当JavaScript应用程序运行时,其中31个核心都处于空闲状态。 JavaScript的单线程避免了复杂的并发情况。如果两个线程同时尝试进行不兼容的更改会发生什么?例如,一个浏览器可能在另一个线程重定向到新的URL并从内存中清除该文档时正在更新DOM。Node.js、Deno和Bun都继承了浏览器的单线程引擎。 这不是JavaScript特定的限制。大多数语言都是单线程的,但像PHP和Python这样的Web选项通常在Web服务器上运行,为每个用户请求启动解释器的新线程的不同实例。这会消耗资源,因此Node.js应用程序通常定义自己的Web服务器,该服务器在单个线程上运行,并异步处理每个传入的请求。 Node.js的方法在处理高流量负载时可能更高效,但长时间运行的JavaScript函数会消耗效率收益。 在我们演示如何使用Web Workers解决执行速度问题之前,我们首先要了解JavaScript的运行方式以及为什么长时间运行的函数会引发问题。 JavaScript非阻塞I/O事件循环 你可能会认为一次只做一件事会导致性能瓶颈,但JavaScript是异步的,这就避免了大部分单线程处理问题,因为: 不需要等待用户在网页上点击按钮。 浏览器触发一个事件,当点击发生时调用JavaScript函数。 不需要等待Ajax请求的响应。 浏览器触发一个事件,在服务器返回数据时调用JavaScript函数。 Node.js应用程序不需要等待数据库查询的结果。 运行时在数据可用时调用JavaScript函数。 JavaScript引擎运行一个事件循环。一旦代码的最后一条语句执行完毕,运行时会循环回来,检查是否有未完成的定时器、待处理的回调函数和数据连接,然后根据需要执行回调函数。 其他操作系统处理线程负责调用诸如HTTP请求、文件处理和数据库连接等输入/输出系统。它们不会阻塞事件循环。事件循环可以继续执行下一个在队列中等待的JavaScript函数。 实质上,JavaScript引擎的主要责任是运行JavaScript代码。操作系统处理所有其他可能导致引擎调用JavaScript函数的I/O操作。 长时间运行的JavaScript函数 JavaScript函数通常由事件触发。它们会进行一些处理,输出一些数据,并且大部分情况下会在毫秒级别内完成,以便事件循环可以继续运行。 不幸的是,一些长时间运行的函数可能会阻塞事件循环。想象一下,你正在开发自己的图像处理函数(如锐化、模糊、灰度化等)。异步代码可以从文件中读取(或写入)数百万字节的像素数据,这对JavaScript引擎影响很小。然而,处理图像的JavaScript代码可能需要几秒钟来计算每个像素。该函数会阻塞事件循环,直到它完成,其他JavaScript代码无法运行。 在浏览器中,用户将无法与页面进行交互。他们无法点击、滚动或输入,并且可能会看到一个“脚本无响应”的错误,其中有停止处理的选项。 对于Node.js服务器应用程序而言,情况更糟糕。在函数执行时,它无法响应其他请求。如果函数需要花费10秒钟来完成,那么在那个时间点访问的每个用户都必须等待最多10秒钟,即使他们没有处理图像。 你可以通过将计算拆分为较小的子任务来解决这个问题。下面的代码使用传递的imageFn函数,最多处理1,000个像素(从一个数组中)。然后,它使用1毫秒的setTimeout延迟调用自身。事件循环的阻塞时间较短,这样JavaScript引擎可以在迭代之间处理其他传入的事件: // pass callback function, image processing function, and input image // return an output image to a callback function function processImage( callback, imageFn = i => {}, imageIn = [] ) { const chunkSize = 1000; // pixels to process per iteration // process first chunk let imageOut = [], pointer = 0; processChunk(); // process a chunk of data function processChunk() { const pointerEnd = pointer + chunkSize; // pass chunk to image processing function imageOut = imageOut.concat( imageFn( imageIn.slice( pointer, pointerEnd ) ) ); if (pointerEnd < imageIn.length) { // process next chunk after a short delay pointer = pointerEnd; setTimeout(processChunk, 1); } else if (callback) { // complete - return output image to callback function callback( null, imageOut ); } } } 这种方法可以防止脚本无响应,但不总是切实可行的。单个执行线程仍然会执行所有的工作,即使CPU可能有更多的处理能力。为了解决这个问题,我们可以使用Web Workers。 ...

October 16, 2024