多线程是现代软件开发中用于增强应用的性能和响应能力的重要技术。然而,JavaScript 是一门单线程语言,它天生是不支持多线程的。为了克服这一限制,引入了 Web Workers。本文就来探讨 Web Workers 对 Web 多线程的重要性,以及使用它们的限制和注意事项。
const worker = new Worker('worker.js');3.向worker对象添加事件侦听器以处理主线程和工作线程之间发送的消息。onmessage 用于处理从工作线程发送来的消息,postMessage 用于向工作线程发送消息。
worker.onmessage = function(event) { console.log('Worker: ' + event.data); }; worker.postMessage('Hello, worker!');4.在 Web Worker 的 JavaScript 文件中,使用self对象的onmessage属性添加一个事件监听器来处理从主线程发出的消息。可以使用event.data属性访问发送的消息数据。
self.onmessage = function(event) { console.log('Main: ' + event.data); self.postMessage('Hello, Main!'); };接下来就运行应用并测试 Worker。可以在控制台看到以下信息,表示主线程和 Worker 线程之间发送和接收了消息。
Main:Hello worker! Worker:Hello Main!我们可以使用terminate()函数来终止一个工作线程,或者通过调用self上的close()函数使其自行终止。
// 从应用中终止一个工作线程 worker.terminate(); // 让一个工作线程自行终止 self.close();可以使用importScripts()函数将库或文件导入到工作线程中,该函数可以接受多个文件。以下示例将script1.js和script2.js加载到工作线程 worker.js 中:
importScripts('script1.js','script2');可以使用 onerror函数来处理工作线程抛出的错误:
worker.onerror = function(err) { console.log("遇到错误") }
const worker = new Worker('./worker.js', { workerData: { a: 1, b: 2, c: 3 } });与浏览器中的 Web Worker 不同, 它在启动时无需运行worker.postMessage()。如果需要的话,可以调用该方法并稍后发送更多数据,它会触发parentPort.on('message')事件处理程序:
parentPort.on('message', e => { console.log(e); });一旦工作线程完成处理,它会使用以下方法将结果数据发送回主线程:
parentPort.postMessage(result);这将在在主脚本中触发 message 事件,主线程接收到 worker 返回的结果:
worker.on('message', result => { console.log( result ); });在发送完消息后,worker 就会终止。这也会触发一个exit事件,如果希望运行清理或其他函数,可以利用这个事件:
worker.on('exit', code => { //堆代码 duidaima.com });除此之外,还支持其他事件处理:
import { Worker, isMainThread, workerData, parentPort } from "node:worker_threads"; if (isMainThread) { // 主线程 const worker = new Worker(import.meta.url, { workerData: { a: 1, b: 2, c: 3 } }); worker.on('message', msg => {}); worker.on('exit', code => {}); } else { // 工作线程 const result = runSomeProcess( workerData ); parentPort.postMessage(result); }这种方式更快,并且对于小型、自包含的单脚本项目来说是一个选择。如果是大型项目,将 worker 脚本文件分开会更容易维护。
// main.js import { Worker } from "node:worker_threads"; const buffer = new SharedArrayBuffer(100 * Int32Array.BYTES_PER_ELEMENT), value = new Int32Array(buffer); value.forEach((v,i) => value[i] = i); const worker = new Worker('./worker.js'); worker.postMessage({ value });工作线程可以接收 value对象:
// worker.js import { parentPort } from 'node:worker_threads'; parentPort.on('message', value => { value[0] = 100; });主线程或工作线程都可以更改值数组中的元素,数据将在两个线程之间保持一致。这可能会提高性能,但有一些缺点:
// 创建一个新的 Web Worker const worker = new Worker('worker.js'); // 定义一个函数来处理来自Web Worker的消息 worker.onmessage = function(event) { const result = event.data; console.log(result); }; // 向Web Worker发送一个消息,以启动计算 worker.postMessage({ num: 1000000 });在 worker.js 中:
// 定义一个函数来执行计算 function compute(num) { let sum = 0; for (let i = 0; i < num; i++) { sum += i; } return sum; } // 定义一个函数来处理来自主线程的消息 onmessage = function(event) { const num = event.data.num; const result = compute(num); postMessage(result); };在这个例子中,创建了一个新的 Web Worker,并定义了一个函数来处理来自 Web Worker 的消息。然后,向 Web Worker 发送一条消息,并提供一个参数(num),指定要执行计算的迭代次数。Web Worker 接收到这条消息后,在后台执行计算。当计算完成后,Web Worker 向主线程发送一条包含结果的消息。主线程收到这个消息后,将结果记录到控制台中。
// 创建一个新的 Web Worker const worker = new Worker('worker.js'); // 定义一个函数来处理来自Web Worker的消息 worker.onmessage = function(event) { const response = event.data; console.log(response); }; // 向Web Worker发送一个消息,以启动计算 worker.postMessage({ urls: ['https://api.example.com/foo', 'https://api.example.com/bar'] });
在 worker.js 中:
// 定义一个函数来执行网络请求 function request(url) { return fetch(url).then(response => response.json()); } // 定义一个函数来处理来自主线程的消息 onmessage = async function(event) { const urls = event.data.urls; const results = await Promise.all(urls.map(request)); postMessage(results); };在这个例子中,创建一个新的 Web Worker 并定义一个函数来处理来自该 Worker 的消息。然后,向 Worker 发送一个包含一组 URL 请求的消息。Worker 接收到这个消息后,在后台使用 fetch API 执行请求。当所有请求完成后,Worker 向主线程发送包含结果的消息。主线程接收到这个消息后,将结果记录到控制台中。
// 创建三个新的 Web Worker const worker1 = new Worker('worker.js'); const worker2 = new Worker('worker.js'); const worker3 = new Worker('worker.js'); // 定义三个处理来自 worker 的消息的函数 worker1.onmessage = handleWorkerMessage; worker2.onmessage = handleWorkerMessage; worker3.onmessage = handleWorkerMessage; function handleWorkerMessage(event) { const result = event.data; console.log(result); } // 将任务分配给不同的 worker 对象,并发送消息启动计算 worker1.postMessage({ num: 1000000 }); worker2.postMessage({ num: 2000000 }); worker3.postMessage({ num: 3000000 });在 worker.js 中:
// 定义一个函数来执行单个计算 function compute(num) { let sum = 0; for (let i = 0; i < num; i++) { sum += i; } return sum; } // 定义一个函数来处理来自主线程的消息 onmessage = function(event) { const result = compute(event.data.num); postMessage(result); };在这个例子中,创建三个新的 Web Worker 并定义一个函数来处理来自该 Worker 的消息。然后,向三个 Worker 分别发送一个要计算的数字消息。Worker 接收到这个消息后执行计算。当计算完成后,Worker 向主线程发送包含结果的消息。主线程接收到这个消息后,将结果记录到控制台中。
worker.on('message', result => { console.log( result ); });消息批处理
// 创建一个消息队列累积消息 const messageQueue = []; // 创建一个将消息添加到队列的函数 function addToQueue(message) { messageQueue.push(message); // 检查队列是否达到阈值大小 if (messageQueue.length >= 10) { // 如果是,请将批处理消息发送到主线程 postMessage(messageQueue); // 清除消息队列 messageQueue.length = 0; } } // 将消息添加到队列中 addToQueue({type: 'log', message: 'Hello, world!'}); // 再添加另一条消息到队列中 addToQueue({type: 'error', message: 'An error occurred.'});在这个例子中, 创建了一个消息队列,用于累积需要发送到主线程的消息。每当使用addToQueue函数将消息添加到队列时,检查队列是否已达到阈值大小(10)。如果是,则使用postMessage方法将批处理消息发送到主线程。然后,清除消息队列,以准备进行下一次批处理。
// 在Web Worker中 self.addEventListener('message', (event) => { if (event.data.action === 'start') { // 使用setTimeout来异步执行计算 setTimeout(() => { const result = doSomeComputation(event.data.data); // 将结果发送回主线程 self.postMessage({ action: 'result', data: result }); }, 0); } });注意内存使用情况
// 在Web Worker中 self.addEventListener('message', (event) => { if (event.data.action === 'start') { // 使用for循环处理一个数据数组 const data = event.data.data; const result = []; for (let i = 0; i < data.length; i++) { // 处理数组中的每个项,并将结果添加到结果数组中 const itemResult = processItem(data[i]); result.push(itemResult); } // 将结果发送回主线程 self.postMessage({ action: 'result', data: result }); } });在这段代码中,Web Worker 处理一个数据数组,并使用postMessage方法将结果发送回主线程。然而,用于处理数据的 for 循环可能耗时较长。导致这个问题的原因是代码一次性处理了整个数据数组,这意味着所有的数据都必须同时加载到内存中。如果数据集非常大,这可能导致 Web Worker 消耗大量的内存,甚至超过浏览器为 Web Worker 分配的内存限制。
大多数现代浏览器都支持 Web Worker,但某些较旧的浏览器可能不支持它们。 为了确保与各种浏览器的兼容性,应该在不同的浏览器和版本中测试 Web Worker 代码。 还可以使用功能检测来检查 Web Worker 是否受支持,然后再在代码中使用它们,如下所示:
if (typeof Worker !== 'undefined') { const worker = new Worker('worker.js'); } else { console.log('Web Workers are not supported in this browser.'); }这段代码会检查当前浏览器是否支持 Web Workers,并在支持时创建一个新的 Web Worker。如果 Web Workers 不受支持,则该代码记录一条消息到控制台,表示该浏览器不支持 Web Workers。
参考资料
[1]WorkerDOM: https://github.com/ampproject/worker-dom