3
头图

今天给大家分享 【探索】Web Worker在Web上实现多线程处理的潜力 ,嘎嘎的😍,看下面

一、多线程是神马💭?

😍多线程是现代软件开发中用于增强应用程序性能和响应能力的重要技术。但是,由于 JavaScript 的单线程特性,这在 Web 上并不常见。为了克服这一限制,引入了 Web Worker 作为在 Web 应用程序中启用此技术的一种方式。在本文中,探讨了 Web Worker 在 Web 上多线程处理的重要性,包括使用它们的局限性和注意事项,以及缓解与 Web Worker 相关的潜在问题的策略。

二、Web Worker又是神马💭?

Web Worker 是现代 Web 开发的一项强大功能,于 [5 年前作为 HTML2009 规范]的一部分引入。它们旨在提供一种在后台执行 JavaScript 代码的方法,与网页的主执行线程分开,以提高性能和响应能力。

主线程是负责呈现 UI、执行 JavaScript 代码和处理用户交互的单个执行上下文。换句话说,JavaScript 是“单线程”的。这意味着任何耗时的任务(例如执行的复杂计算或数据处理)都会阻塞主线程并导致 UI 冻结并变得无响应。

这就是 Web Workers 的用武之地。

Web Worker 的实现是为了解决这个问题,它允许在单独的线程(称为工作线程)中执行耗时的任务。这使得 JS 代码能够在后台执行,而不会阻塞主线程并导致页面无响应。

三、 创建 web worker

在 JS 中创建 Web Worker 并不是一项复杂的任务。以下步骤提供了将 Web Worker 集成到应用程序中的起点👀:

  1. 创建一个新的 JS 文件,其中包含要在工作线程中运行的代码。此文件不应包含对 DOM 的任何引用,因为它无权访问它。
  2. 在主 JS 文件中,使用构造函数创建一个新的 worker 对象。此构造函数采用单个参数,该参数是在步骤 1 中创建的 JavaScript 文件的 URL。Worker

    const worker = new Worker('worker.js');
  3. 将事件侦听器添加到工作线程对象,以处理在主线程和工作线程之间发送的消息。事件处理程序用于处理从工作线程发送的消息,而该方法用于向工作线程发送消息。onmessage postMessage

    worker.onmessage = function(event) {
      console.log('Worker said: ' + event.data);
    };
    worker.postMessage('Hello, worker!');
  4. 在JS 文件中,添加一个事件侦听器,以使用对象的属性处理从主线程发送的消息。你可以使用该属性访问随消息一起发送的数据。onmessage self event.data

    self.onmessage = function(event) {
      console.log('Main thread said: ' + event.data);
      self.postMessage('Hello, main thread!');
    };

    现在,让我们运行 Web 应用程序并测试工作线程。我们应该看到打印到控制台的消息,表明消息在主线程和工作线程之间发送和接收。✔

image.png
Web Worker 和主线程之间的一个关键区别是 Web Worker 无法访问 DOM 或 UI。这意味着他们不能直接操作页面上的 HTML 元素或与用户交互。

😓请注意 [ Web Worker 旨在执行不需要直接访问 UI 的任务,例如数据处理、图像处理或计算。]

另一个重要的区别是,Web Worker 被设计为在与主线程分开的沙盒环境中运行,这意味着它们对系统资源的访问有限,并且无法访问某些 API,例如[localStorage]或[sessionStorage]。但是,它们可以通过消息传递系统与主线程进行通信,从而允许在两个线程之间交换数据。

四、 Web Worker在Web上多线程的重要性和好处

Web Workers 为 Web 开发人员提供了一种在 Web 上实现多线程的方法,这对于构建高性能 Web 应用程序至关重要。通过允许在后台执行耗时的任务,与主线程分开,Web Worker 提高了网页的整体响应能力,并允许更无缝的用户体验。以下是 Web Worker 在 Web 上进行多线程处理的一些重要性和好处。👀

1. 提高资源利用率

通过允许在后台执行耗时的任务,Web Worker 可以更有效地利用系统资源,从而更快、更高效地处理数据并提高整体性能。这对于涉及大量数据处理或图像处理的 Web 应用程序尤为重要,因为 Web Worker 可以在不影响用户界面的情况下执行这些任务。

2. 提高稳定性和可靠性

通过将耗时的任务隔离在单独的工作线程中,Web Worker 有助于防止在主线程上执行大量代码时可能发生的崩溃和错误。这使开发人员更容易编写稳定可靠的 Web 应用程序,从而减少用户沮丧或数据丢失的可能性。😂

3. 增强的安全性

Web Worker 在与主线程分开的沙盒环境中运行,这有助于增强 Web 应用程序的安全性。这种隔离可防止恶意代码访问或修改主线程或其他 Web Worker 中的数据,从而降低数据泄露或其他安全漏洞的风险。

4. 提高资源利用率

它也可以释放主线程来处理用户输入和其他任务,而 Web Worker 则在后台处理耗时的计算,从而帮助提高资源利用率。这有助于提高整体系统性能并降低崩溃或错误的可能性。此外,通过利用多个 CPU 内核,Web Worker 可以更有效地利用系统资源,从而更快、更高效地处理数据。👍

 还可以更好地平衡和扩展 Web 应用程序。通过允许跨多个工作线程并行执行任务, 可以帮助将工作负载均匀地分布在多个内核或处理器之间,从而实现更快、更高效的数据处理。这对于遇到高流量或需求的 Web 应用程序尤为重要,因为 Web Worker 可以帮助确保应用程序能够在不影响性能的情况下处理增加的负载。

五、Web Worker 的实际应用

让我们探讨一下 Web Workers 的一些最常见和最有用的应用程序。无论你是在构建复杂的 Web 应用程序还是简单的网站,了解如何利用他都可以帮助你提高性能并提供更好的用户体验。

1. 卸载 CPU 密集型工作

假设我们有一个 Web 应用程序,需要执行大型 CPU 密集型计算。如果我们在主线程中执行此计算,用户界面将变得无响应,用户体验将受到影响。为了避免这种情况,我们可以使用 Web Worker 在后台执行计算。

// 创建一个新的Web Worker。
const worker = new Worker('worker.js');

// 定义一个函数来处理来自工作线程的消息。
worker.onmessage = function(event) {
  const result = event.data;
  console.log(result);
};

// 向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,并定义了一个函数来处理来自该 worker 的消息。然后,我们向工作线程发送一条消息,其中包含一个参数 (num),该参数指定要在计算中执行的迭代次数。工作线程收到此消息并在后台执行计算。计算完成后,工作线程将一条消息连同结果一起发送回主线程。主线程接收此消息并将结果记录到控制台num

image.png

此任务涉及将所有数字相加到给定数字。虽然对于小数字来说,这项任务相对简单明了,但对于非常大的数字来说,它可能会变得计算密集型。

在上面使用的示例代码中,我们将数字传递给 Web Worker 中的函数。这意味着计算函数需要将 0 到 万之间的所有数字相加。这涉及大量其他操作,可能需要大量时间才能完成,尤其是当代码在速度较慢的计算机上或已忙于其他任务的浏览器选项卡中运行时。1000000 compute()

通过将此任务卸载到 Web Worker,应用程序的主线程可以继续平稳运行,而不会被计算密集型任务阻塞。这允许用户界面保持响应,并确保可以毫不延迟地处理其他任务,例如用户输入或动画。😂

2. 处理网络请求

让我们考虑一个场景,其中 Web 应用程序需要启动大量网络请求。在主线程中执行这些请求可能会导致用户界面无响应,并导致用户体验不佳。为了防止这个拉胯的问题,我们可以利用 Web Workers 在后台处理这些请求。这样一来,主线程就可以自由执行其他任务,而 Web Worker 则同时处理网络请求,从而提高性能并改善用户体验。
look!!👀

// 创建一个新的Web Worker。
const worker = new Worker('worker.js');

// 定义一个函数来处理来自工作线程的消息。
worker.onmessage = function(event) {
  const response = event.data;
  console.log(response);
};

//向worker发送消息以启动请求。
worker.postMessage({ urls: ['https://juejin.cn/', 'https://juejin.cn/pins'] });

// 在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 的消息。然后,我们向工作线程发送一条消息,其中包含要请求的 URL 数组。工作线程收到此消息,并使用fetch应用程序接口.当所有请求完成后,工作线程将一条消息与结果一起发送回主线程。主线程接收此消息并将结果记录到控制台。

3. 并行处理

假设我们有一个 Web 应用程序,需要执行大量独立的计算。如果我们在主线程中按顺序执行这些计算,用户界面将变得无响应,用户体验将受到影响。为了避免这种情况,我们可以使用 Web Worker 并行执行计算。👀

const worker = new Worker('worker.js');

worker.onmessage = function(event) {
  const result = event.data;
  console.log(result);
};

// 向worker发送消息以启动计算
worker.postMessage({ nums: [1000000, 2000000, 3000000] });

// 在worker.js里面:
//定义一个函数来执行单个计算
function compute(num) {
  let sum = 0;
  for (let i = 0; i < num; i++) {
    sum += i;
}
  return sum;
}

// 定义一个函数来处理来自主线程的消息
onmessage = function(event) {
  const nums = event.data.nums;
  const results = nums.map(compute);
  postMessage(results);
};

在此示例中,我们创建了Web Worker,并定义了一个函数来处理来自该 worker 的消息。然后,我们向工作线程发送一条消息,其中包含要计算的数字数组。工作线程接收此消息并使用 map 方法并行执行计算。当所有计算完成后,工作线程将一条消息与结果一起发送回主线程。主线程接收此消息并将结果记录到控制台。

六、 限制和注意事项

Web Worker 虽然是提高 Web 应用程序性能和响应能力的强大工具,但它们也有一些限制和注意事项,在使用它们时应牢记这些限制和注意事项。以下是一些最重要的:

1. 浏览器支持

所有主流浏览器都支持 Web Worker,包括 Chrome、Firefox、Safari 和 Edge。但是,还有一些其他浏览器不支持 Web Worker,或者可能支持有限。

有关浏览器支持的更广泛了解,请参阅兼容性表格

在生产代码中使用任何功能之前,请务必检查浏览器对任何功能的支持,并彻底测试你的应用程序以确保兼容性。

2. 对 DOM 的有限访问

Web Worker 在单独的线程中运行,并且无权访问 DOM 或主线程中的其他全局对象。这意味着你不能直接从 Web Worker 操作 DOM,也不能访问窗口或文档等全局对象。

若要解决此限制,可以使用该方法与主线程通信并更新 DOM 或间接访问全局对象。例如,你可以使用 DOM 或全局对象将数据发送到主线程,然后更新 DOM 或全局对象以响应消息。postMessage postMessage

或者,有一些库可以帮助解决此问题。例如,WorkerDOM 库使你能够在 Web Worker 中运行 DOM,从而加快页面呈现速度并提高性能。

3. 通信开销

Web Worker 使用postMessage方法,因此可能会引入通信开销,这是指在两个或多个计算系统之间建立和维护通信所需的时间和资源量,例如在 Web Worker 和 Web 应用程序中的主线程之间。这可能会导致处理消息的延迟,并可能减慢应用程序的速度。为了最大程度地减少此开销,应仅在线程之间发送基本数据,并避免发送大量数据或频繁发送消息

4. 有限的调试工具

调试 Web Worker 可能比在主线程中调试代码更具挑战性,因为可用的调试工具较少。为了简化调试,可以使用 API 记录来自工作线程的消息,并使用浏览器开发人员工具检查线程之间发送的消息。console

5. 代码复杂度

使用 Web Worker 可能会增加代码的复杂性,因为你需要管理线程之间的通信并确保正确传递数据。这可能会使编写、调试和维护代码变得更加困难,因此你应该仔细考虑应用程序是否需要使用 Web Worker。

七、 缓解 Web Worker 潜在问题的策略

Web Worker 虽然提高 Web 应用程序性能和响应能力的强大工具。但是,在使用时,可能会出现几个潜在问题。以下是缓解这些问题的一些策略:

1. 通过消息批处理将通信开销降至最低

消息批处理涉及将多条消息分组为单个批处理消息,这比单独发送单个消息更有效。这种方法减少了主线程和 Web Worker 之间的往返次数。它可以帮助最大程度地减少通信开销并提高 Web 应用程序的整体性能。

为了实现消息批处理,你可以使用队列来累积消息,并在队列达到一定阈值或设置的时间段后批量发送消息。下面是如何在 Web Worker 中实现消息批处理的示例:

// 创建消息队列来累积消息
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.'});

在本例中,我们创建了一个消息队列来累积需要发送到主线程的消息。每当使用该函数将消息添加到队列中时,我们都会检查队列是否达到阈值大小(在本例中为10条消息)。如果是,我们使用该方法将批处理消息发送到主线程。最后,我们清除消息队列,为下一批处理做准备。
通过以这种方式批处理消息,我们可以减少主线程和Web worker之间发送的消息总数,是吧?😎

2. 避免使用同步方法

这些是 js 函数或操作,用于阻止其他代码的执行,直到它们完成。同步方法可能会阻塞主线程,并导致应用程序无响应。为避免这种情况,应避免在 Web Worker 代码中使用同步方法。相反,请使用异步方法来执行长时间运行的计算。setTimeout() setInterval()

下面是一个小小的演示:

self.addEventListener('message', (event) => {
  if (event.data.action === 'start') {
    // 使用setTimeout来异步执行一些计算
    setTimeout(() => {
      const result = doSomeComputation(event.data.data);

      // 将结果发送回主线程
      self.postMessage({ action: 'result', data: result });
    }, 0);
  }
});

3. 注意内存使用情况

Web Worker 有自己的内存空间,该内存空间可能会受到限制,具体取决于用户的设备和浏览器设置。为了避免内存问题,你应该注意 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 的内存限制。😓

若要缓解此问题,可以考虑使用例如forEachreduce,它可以一次处理一个项目的数据,并且避免了一次将整个数组加载到内存中的需要。

4. 浏览器兼容性

大多数现代浏览器都支持 Web Worker,但某些较旧的浏览器可能不支持它们。为确保与各种浏览器兼容,应在不同的浏览器和版本中测试 Web Worker 代码。在代码中使用 Web Worker 之前,还可以使用功能检测来检查是否支持 Web Worker,如下所示:

if (typeof Worker !== 'undefined') {
  // 支持Web worker
  const worker = new Worker('worker.js');
} else {
  // 不支持
  console.log('Web Workers are not supported in this browser.');
}

此代码检查当前浏览器是否支持 Web Worker,如果支持它们,则创建新的 Web Worker。如果不支持,则代码会向控制台记录一条消息,指示浏览器不支持。

通过遵循这些策略,可以确保 Web Worker 代码高效、响应迅速且与各种浏览器兼容。

八、 总结下

随着 Web 应用程序变得越来越复杂和要求越来越高,高效的多线程技术(如 Web Worker)的重要性可能会增加。Web Worker 是现代 Web 开发的一项基本功能,它允许开发人员将 CPU 密集型任务卸载到单独的线程,从而提高应用程序性能和响应能力。但是,在使用 Web Worker 时,需要牢记一些重要的限制和注意事项,例如`无法访问 DOM 以及限制可在线程之间传递的数据类型。
`
为了缓解这些潜在问题,我们开发人员可以遵循前面提到的策略,例如使用异步方法并注意要卸载的任务的复杂性。

我觉得吧,未来,使用 Web Worker 进行多线程处理可能仍将是提高 Web 应用程序性能和响应能力的重要技术。虽然还有其他技术可以在 js 中实现多线程,例如使用 WebSockets 或SharedArrayBuffer,但Web Workers 有几个优点,足以使它们成为开发人员的强大工具。

采用 WebAssembly 等最新技术可能会为使用 Web Workers 卸载更复杂和计算密集型任务开辟新的机会。总体而言,Web Worker 可能会在未来几年继续发展和改进,帮助开发人员创建更高效、响应更迅速的 Web 应用程序。

此外,还有许多库和工具可以帮助开发人员使用 Web Worker。例如,Comlink 和 Workerize 提供了用于与 Web Worker 通信的简化 API。这些库抽象化了管理 Web Worker 的一些复杂性,使其更容易利用其优势。

好了,分享就到这里了😂

希望本文能让jym很好地了解 Web Worker 在多线程方面的潜力,以及如何在你自己的代码中使用它们。

感谢jym浏览本文,共同进步🤞,若有更好的建议,欢迎评论区讨论哈🌹。

如果觉得这篇干货很有用,可以加个关关🥰,点个赞赞🥰,后面我会继续卷,分享更多干货!感谢支持!😍

本文参与了SegmentFault 思否写作挑战赛活动,欢迎正在阅读的你也加入。

月弦笙音
48 声望5 粉丝

前端开荒