Web Workers 学习笔记

最近在开发中遇到了一个需求,一大堆的图片都需要调用两个接口。这对单线程的 JavaScript 运行环境构成了挑战,容易影响用户体验。所以决定学习 Web Workers 并记录一下。

Web Workers 的作用就是提供一个多线程环境,允许将一些繁重任务(涉及大量计算或网络请求时)从主线程分离出来。在主线程创建的 Worker 线程中,这些任务可在后台运行,避免阻塞主线程,使主线程可以更专注于 UI 交互等实时响应,从而大幅提升应用的流畅性。

Worker 一旦创建,会在后台持续运行,不受主线程活动(如用户点击或提交)打扰,直到完成任务后将结果返回给主线程。这种处理方式大大改善了用户体验。但需要注意的是,Worker 会占用额外资源,合理使用、及时关闭非常重要。

兼容性

兼容性良好,可以放心使用。

注意事项

1. 无法直接操作 DOM

        Web Workers 运行在独立的线程中,不能直接访问主线程的 DOM,也无法使用 document、window、parent 这些对象。与 DOM 相关的更新需要通过消息传递将数据返回主线程来完成。

Worker 线程可以使用 navigator 对象和 location 对象

navigator 可以访问网络状态等信息,这些属性在 Worker 线程中是可访问的,并且与主线程中的 navigator 保持一致。

// worker.ts
console.log(navigator.userAgent); // 可用
console.log(navigator.language);  // 可用
console.log(navigator.onLine);    // 可用

location 可用来读取 Worker 文件的 URL,但它与主线程的 window.location 有所不同。Worker 的 location 是只读的,通常称为 WorkerLocation,提供了 Worker 脚本的 URL 信息,而非页面的 URL。

以下是它提供的属性:

  • location.href:Worker 脚本的完整 URL
  • location.protocol:协议(如 https:
  • location.host:主机名和端口
  • location.hostname:主机名
  • location.port:端口号
  • location.pathname:路径名
  • location.search:查询字符串
  • location.hash:哈希值

这些属性都是只读的,因此无法在 Worker 中通过 location 重定向。

// worker.ts
console.log(location.href);       // Worker 脚本的 URL
console.log(location.origin);     // 脚本的来源,如 "https://example.com"
console.log(location.pathname);   // Worker 文件的路径

2. 数据通信通过消息传递

        主线程和 Worker 不在同一个上下文环境,他们之间的通信使用 `postMessage` 方法发送消息,并通过 `onmessage` 接收消息。由于 Worker 不共享内存,数据会被序列化和反序列化,因此传输大量数据时性能可能受影响。

3. 没有同步 API

        Web Workers 中,出于性能和隔离的考虑,一些同步 API 被禁用了,特别是涉及阻塞线程和用户交互的 API,比如 alert()confirm()prompt(),以及涉及本地存储的 localStorage

被禁用原因:

  • Web Worker 是为后台计算和处理任务设计的,不应该在执行过程中阻塞或打断自己或主线程的操作。
  • Worker 线程没有访问页面 UI 的权限,因此也无法创建原生的弹框,这些操作应交由主线程完成。
  • localStorage 是一种同步 API,存储和读取数据会在调用时立即完成,且会阻塞线程。

解决方案

  • 如果 Worker 需要获得用户输入或确认信息,可以通过消息传递通知主线程,让主线程显示弹框并将结果返回给 Worker。
  • 使用异步存储 API,例如 IndexedDB 或将数据传递给主线程,通过主线程访问 localStorage

 4. 需要同源

        Worker 脚本文件必须与页面在同一个源下进行加载,或者以绝对路径加载。

5. 消耗系统资源

        每个 Worker 创建都会占用系统资源,包括内存和线程。对于简单任务,不建议创建大量 Worker,因为资源开销会变大。建议重用 Worker 或限制并发 Worker 数量。

  • 重用 Worker:

        使用单个 Worker 处理多个任务。如果任务不需要并行处理,并且可以顺序执行,可以重用同一个 Worker。每次任务完成后,通过消息通知主线程,主线程再发送新的任务到同一个 Worker 中。

// worker.ts
self.onmessage = (event) => {const { task, data } = event.data;let result;// 根据任务类型执行不同操作switch (task) {case 'task1':result = data * 2;break;case 'task2':result = data + 10;break;default:result = 'unknown task';}// 将结果返回主线程self.postMessage({ task, result });
};
const worker = new Worker('worker.ts');worker.onmessage = (event) => {console.log(`Result for ${event.data.task}:`, event.data.result);// 执行下一个任务sendNextTask();
};function sendNextTask() {const task = getNextTask(); // 假设从队列获取下一个任务if (task) {worker.postMessage(task);}
}const taskQueue = [{ type: 'task1', data: 5 },{ type: 'task2', data: 10 },{ type: 'task3', data: 20 }
];function getNextTask() {return taskQueue.shift(); // 从任务队列中取出第一个任务
}// 初始化时发送第一个任务
sendNextTask();

为了节省系统资源,使用完毕需要关闭 Worker。

// 主线程
worker.terminate();// Worker 线程
self.close();
  • 限制并发 Worker 数量:

        创建 Worker 池(Worker 池的逻辑会写在主线程的文件中)。在处理大量任务时,可以创建一个有限数量的 Worker 池,按照需要动态分配任务。这种方式类似于线程池,限制了同时运行的 Worker 数量,避免资源过度消耗。

  • 初始化了 MAX_WORKERS 个 Worker,并将它们放入 workerPool 数组。
  • assignTask 函数从任务队列中取出任务,并将其分配给空闲的 Worker。
  • addTask 函数将任务加入 taskQueue 队列,同时检查是否有空闲 Worker,如果有,则立即分配任务。
const MAX_WORKERS = 4;      // 最大 Worker 数量
const workerPool = [];      // 存储 Worker 实例
const taskQueue = [];       // 存储待处理任务// 初始化 Worker 池
for (let i = 0; i < MAX_WORKERS; i++) {const worker = new Worker('worker.js');worker.isBusy = false;worker.onmessage = (event) => {console.log('Worker result:', event.data);worker.isBusy = false;    // 标记 Worker 空闲assignTask(worker);       // 任务完成后分配下一个任务};workerPool.push(worker);
}// 分配任务给空闲 Worker
function assignTask(worker) {if (taskQueue.length > 0) {const task = taskQueue.shift(); // 获取下一个任务worker.postMessage(task);       // 发送任务到 Workerworker.isBusy = true;           // 标记 Worker 正在工作}
}// 添加任务到队列并尝试分配
function addTask(task) {taskQueue.push(task);              // 添加任务到队列const idleWorker = workerPool.find(w => !w.isBusy);if (idleWorker) {assignTask(idleWorker);          // 立即分配任务给空闲 Worker}
}// 使用 Worker 池处理多个任务
addTask({ type: 'task1', data: 5 });
addTask({ type: 'task2', data: 10 });
addTask({ type: 'task3', data: 20 });
  • 如果 MAX_WORKERS = 4,而 addTask 只调用了一次,那么只有一个 Worker 会处理任务,其余 3 个 Worker 空闲。
  • 如果 addTask 调用三次,MAX_WORKERS = 4 则可以同时分配这 3 个任务到 3 个空闲 Worker。
  • 如果 addTask 调用超过 4 次(比如调用了 6 次),那么前 4 个任务可以立即分配给 4 个 Worker,其余 2 个任务会在 taskQueue 中等待,直到有 Worker 完成任务并变为空闲状态时才继续分配。

6. 网络请求的限制

        Worker 中允许发起 `fetch` 请求,但不支持如 `XMLHttpRequest` 的某些同步网络请求(同步请求会阻塞线程)(但支持异步)。注意 Worker 中的请求与主线程网络策略一致,仍然受 CORS 限制。

7.读取文件的限制

        Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

使用实例

1.创建 Worker 文件(worker.ts)

self 代表子线程自身,即子线程的全局对象。

// worker.ts
self.onmessage = function (event) {const imageUrl = event.data; // 接收主线程传递的图片URL// 模拟图片处理操作fetch(imageUrl).then(response => response.blob()).then(blob => {// 假设对图片进行了处理self.postMessage(blob); // 返回处理结果给主线程}).catch(error => self.postMessage({ error }));
};

2.主线程调用 Worker(main.ts) 

// main.ts
export const processImages = (images: string[]) => {const worker = new Worker('./utils/worker.ts');worker.onmessage = (event) => {const result = event.data;if (result.error) {console.error("Error processing image:", result.error);} else {console.log("Processed image blob:", result);// 在主线程中将结果进行下一步处理,比如显示或上传}};images.forEach(imageUrl => {worker.postMessage(imageUrl); // 将图片URL传递给 Worker});// 可选:在处理完所有任务后,可以通过 worker.terminate() 关闭 Worker 释放资源return () => worker.terminate();
};

3.外部调用 Web Workers 

// imageUpload.ts
import { processImages } from './main';const images = ['url1', 'url2', 'url3'];
const terminateWorker = processImages(images);

报错:

  • 我首先排查是不是 worker.ts 的问题,因此我将 worker.ts 改为 worker.js。 
  • 仍然会报错,就检查引入 worker.ts 的路径问题。
  • 仍然会报错,在 main.ts 中创建 Worker 时,确保使用正确的路径格式和模块类型
// main.ts
const worker = new Worker(new URL('worker.ts', import.meta.url), { type: 'module' });

成功,控制台输出信息:

注意:确保 worker.js 文件没有任何 importexport 语句。 

self.addEventListener() 和 self.onmessage 都可以用。根据主线程发来的数据,Worker 线程可以调用不同的方法:

self.addEventListener('message', function (e) {const data = e.data;switch (data.cmd) {case 'start':self.postMessage('WORKER STARTED: ' + data.msg);break;case 'stop':self.postMessage('WORKER STOPPED: ' + data.msg);self.close(); // 用于在 Worker 内部关闭自身break;default:self.postMessage('Unknown command: ' + data.msg);};
}, false);

Worker 加载脚本

假设有以下几个文件:

  • worker.ts:主 Worker 文件
  • math.ts:包含数学相关的函数
  • stringUtils.ts:包含字符串处理函数
// math.ts
function add(a, b) {return a + b;
}function multiply(a, b) {return a * b;
}
// stringUtils.ts
function capitalize(str) {return str.charAt(0).toUpperCase() + str.slice(1);
}
// worker.ts
importScripts('math.ts', 'stringUtils.ts');self.onmessage = function (event) {const { type, payload } = event.data;if (type === 'calculate') {const result = add(payload.a, payload.b);self.postMessage({ type: 'result', result });}if (type === 'capitalize') {const result = capitalize(payload.text);self.postMessage({ type: 'result', result });}
};
// 主线程
const worker = new Worker('worker.ts');// 监听 Worker 的消息
worker.onmessage = (event) => {console.log('Worker result:', event.data.result);
};// 发送计算任务
worker.postMessage({ type: 'calculate', payload: { a: 5, b: 3 } });// 发送字符串任务
worker.postMessage({ type: 'capitalize', payload: { text: 'hello' } });

错误处理

worker.onerror(function (event) {console.log(['ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join(''));
});// ORworker.addEventListener('error', function (event) {// ...
});

主线程与 Worker 通信

        在 Web Worker 中,主线程与 Worker 之间的通信是通过消息传递实现的,这种通信方式是基于拷贝关系,即传递的是值的副本而不是引用。这意味着主线程和 Worker 中的相同数据是独立的,修改一方的数据不会影响另一方。

        具体来说,浏览器会对通信内容进行序列化(即将数据转为字符串形式),再将其发送给 Worker 线程,Worker 收到后再将数据反序列化为原来的格式。这种机制虽然安全且防止线程之间的数据干扰,但对性能有一定开销。

传递普通数据(文本或对象)

// main.ts(主线程)
const worker = new Worker('worker.ts');// 深拷贝数据
const messageData = { task: 'processData', value: 42 };
const clonedData = JSON.parse(JSON.stringify(messageData)); // 创建深拷贝worker.postMessage(clonedData); // 发送深拷贝数据到 Workerworker.onmessage = (event) => {console.log('Original data in main thread:', messageData); // 验证原始数据未被修改console.log('Message from Worker:', event.data); // 接收 Worker 返回的消息
};
// worker.js(Worker 线程)
self.onmessage = (event) => {const receivedData = event.data;console.log('Worker received:', receivedData);receivedData.value = 100; // 修改数据console.log('Worker received after:', receivedData); // 打印修改后的数据self.postMessage(receivedData); // 返回数据给主线程
};

 

传递二进制数据(ArrayBuffer

        对于二进制数据,主线程和 Worker 之间可以通过Transferable 对象进行零拷贝传递,避免拷贝开销并提高性能。

// main.ts(主线程)
const worker = new Worker('worker.ts');const buffer = new ArrayBuffer(8); // 创建一个 8 字节的二进制缓冲区
const view = new Uint8Array(buffer);
view[0] = 10;// 传递 buffer,使用 Transferable 对象
worker.postMessage(buffer, [buffer]); // 第二个参数是 Transferable 对象console.log(buffer.byteLength); // 输出 0,因为 buffer 已被转移到 Workerworker.onmessage = (event) => {console.log('Modified buffer received from Worker:', event.data);
};
// worker.ts(Worker 线程)
self.onmessage = (event) => {const buffer = event.data;const view = new Uint8Array(buffer);view[0] = 20; // 修改数据self.postMessage(buffer, [buffer]); // 传回给主线程
};

 

        通过 Transferable 对象将 buffer 传递给 Worker,主线程中的 buffer 会被“转移”,即 buffer.byteLength 会变为 0,表示这个 ArrayBuffer 已经转移到 Worker,主线程不再拥有它。这种机制避免了对大数据的拷贝开销,并使传递二进制数据更高效。

 这种转移数据的方法,叫做 Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。

Worker 代码载入

        Web Workers 通过单独的 JavaScript 文件运行,因为 Worker 是独立线程,无法直接访问主线程的作用域和上下文。不过,有时需要在 Worker 中执行一些与主线程相关的代码,比如传递函数或使用主线程中的脚本逻辑。可以通过以下两种方法实现 Worker 和主线程的代码共享:

嵌入 Worker 脚本

在 HTML 页面中使用 <script> 标签嵌入 Worker 脚本内容。

  • 注意:为了避免浏览器将这个脚本当作普通的 JavaScript 执行,type 属性指定为一个不被识别的值(例如 app/worker)。这样浏览器会忽略它,而不会报错。

使用 Blob 载入内联代码

// 载入与主线程在同一个网页的代码
<!DOCTYPE html>
<html><body><!-- 嵌入 Worker 代码 --><script id="worker" type="app/worker">addEventListener('message', function () {postMessage('some message');}, false);</script><script>// 获取嵌入的 Worker 代码const blob = new Blob([document.querySelector('#worker').textContent], { type: 'application/javascript' });// ORconst workerCode = `self.onmessage = function(event) {console.log('Worker received:', event.data);self.postMessage('Hello from inline Worker');`;// 创建 Blob 对象并生成 URLconst blob = new Blob([workerCode], { type: 'application/javascript' });const url = URL.createObjectURL(blob);// 创建 Workerconst worker = new Worker(url);// 监听 Worker 消息worker.onmessage = function (e) {console.log(e.data); // 输出 'some message'};// 向 Worker 发送消息,触发 Worker 的 `message` 事件worker.postMessage('Hello Worker');</script></body>
</html>

 使用 Data URL 载入内联代码

// main.ts
const worker = new Worker(`data:text/javascript,self.onmessage = function(event) {console.log('Worker received:', event.data);self.postMessage('Hello from Data URL Worker');};`
);worker.onmessage = (event) => {console.log('Message from Worker:', event.data);
};worker.postMessage('Hello Worker');

Worker 中实现轮询任务

// worker.ts
// 设置轮询的时间间隔(毫秒)
const POLLING_INTERVAL = 5000;// 模拟一个轮询函数
function poll() {fetch('https://api.example.com/data') // 假设这个 URL 是服务器端的 API.then(response => response.json()).then(data => {// 将数据返回给主线程postMessage(data);// 假设某种条件满足时停止轮询if (data.status === 'complete') {close(); // 关闭 Worker,停止轮询} else {// 否则,继续下次轮询setTimeout(poll, POLLING_INTERVAL);}}).catch(error => {postMessage({ error: error.message });setTimeout(poll, POLLING_INTERVAL); // 即使出错也继续轮询});
}// 启动轮询
poll();

属性和方法

  • Worker.onerror:指定 error 事件的监听函数。
  • Worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
  • Worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • Worker.postMessage():向 Worker 线程发送消息。
  • Worker.terminate():立即终止 Worker 线程。

 Web Workers 的全局对象

        Web Workers 有自己的全局对象,不是主线程的window,而是一个专门为 Worker 定制的全局对象。因此定义在window上面的对象和方法不是全部都可以使用。

  • self.name: Worker 的名字。该属性只读,由构造函数指定。
  • self.onmessage:指定message事件的监听函数。
  • self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • self.close():关闭 Worker 线程。
  • self.postMessage():向产生这个 Worker 线程发送消息。
  • self.importScripts():加载 JS 脚本。

扩展:嵌套 Worker

        Web Workers 支持在其内部再创建新的 Worker,这种模式称为 嵌套 Worker(Nested Worker)。它可以帮助更复杂的任务进行进一步分层,并在 Worker 内部将计算任务进一步分解到新的 Worker 中。

兼容性

  • 现代浏览器:大部分现代浏览器支持嵌套 Worker(如 Chrome、Firefox 和 Edge)。
  • Safari:早期版本中不支持嵌套 Worker,因此需要进行兼容性测试。
  • IE 浏览器:不支持嵌套特性。
// main.ts (主线程)
const worker = new Worker('worker.js');worker.onmessage = function (event) {console.log('Message from nested Worker:', event.data);
};worker.postMessage('start'); // 启动嵌套任务

第一层 Worker 文件(worker.ts

接收来自主线程的消息,然后创建第二层嵌套 Worker

// worker.ts
self.onmessage = function (event) {if (event.data === 'start') {// 创建嵌套 Workerconst nestedWorker = new Worker('nestedWorker.ts');// 监听嵌套 Worker 的消息nestedWorker.onmessage = function (e) {// 将嵌套 Worker 的结果传回主线程self.postMessage(e.data);};// 向嵌套 Worker 发送任务nestedWorker.postMessage('do heavy computation');}
};

第二层嵌套 Worker 文件(nestedWorker.ts

这个文件是嵌套 Worker 的代码,处理实际的任务并将结果返回给第一层 Worker。

// nestedWorker.ts
self.onmessage = function (event) {if (event.data === 'do heavy computation') {// 执行一些复杂的任务let result = 0;for (let i = 0; i < 1e6; i++) {result += i;}// 返回结果self.postMessage(`Computation result: ${result}`);}
};
  • 主线程创建了第一个 Worker(worker.ts)。
  • worker.ts 接收主线程的消息,创建了一个嵌套 Worker(nestedWorker.ts)来处理复杂的计算任务。
  • nestedWorker.ts 完成任务后,将结果发送回 worker.ts,再由 worker.ts 将结果传回主线程。

文章参考:

https://www.ruanyifeng.com/blog/2018/07/web-worker.html?20241106192347#comment-last

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API

Web Workers | Can I use... Support tables for HTML5, CSS3, etc

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/6239.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

YOLO11改进|注意力机制篇|引入HAT超分辨率重建模块

目录 一、HAttention注意力机制1.1HAttention注意力介绍1.2HAT核心代码二、添加HAT注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4三、yaml文件与运行3.1yaml文件3.2运行成功截图一、HAttention注意力机制 1.1HAttention注意力介绍 HAT模型 通过结合卷积特征提取与多尺度注意力机…

关于wordpress instagram feed 插件 (现更名为Smash Balloon Social Photo Feed)

插件地址&#xff1a; Smash Balloon Social Photo Feed – Easy Social Feeds Plugin – WordPress 插件 | WordPress.org China 简体中文 安装后&#xff0c;配置教程&#xff1a; Setting up the Instagram Feed Pro WordPress Plugin - Smash Balloon 从这里面开始看就…

ElasticSearch认识

ElasticSearch是什么&#xff1f; Elasticsearch 是一个基于 Apache Lucene 构建的开源分布式搜索引擎和分析引擎。它专为云计算环境设计&#xff0c;提供了一个分布式的、高可用的实时分析和搜索平台。Elasticsearch 可以处理大量数据&#xff0c;并且具备横向扩展能力&#…

在 Google Chrome 上查找并安装 SearchGPT 扩展

ChatGPT 搜索 (SearchGPT)&#xff0c;一个嵌入在流行的 ChatGPT 聊天机器人中的全新搜索引擎&#xff0c;可以改变人们搜索网页的方式。如果你想让它更容易找到并使用它&#xff0c;可以通过安装它的 Chrome 扩展程序。 ChatGPT 搜索是一个快速、精准且无广告的搜索引擎&…

两道算法题

一、算法一 Amazon would like to enforce a password policy that when a user changes their password, the new password cannot be similar to the current one. To determine whether two passwords are similar, they take the new password, choose a set of indices a…

嵌入式硬件电子电路设计(三)电源电路之负电源

引言&#xff1a;在对信号线性度放大要求非常高的应用需要使用双电源运放&#xff0c;比如高精度测量仪器、仪表等;那么就需要给双电源运放提供正负电源。 目录 负电源电路原理 负电源的作用 如何产生负电源 负电源能作功吗&#xff1f; 地的理解 负电压产生电路 BUCK电…

A019基于SpringBoot的校园闲置物品交易系统

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

字节青训-小S的倒排索引

问题描述 小S正在帮助她的朋友们建立一个搜索引擎。为了让用户能够更快地找到他们感兴趣的帖子&#xff0c;小S决定使用倒排索引。倒排索引的工作原理是&#xff1a;每个单词都会关联一个帖子ID的列表&#xff0c;这些帖子包含该单词&#xff0c;且ID按从小到大的顺序排列。 例…

2024 CSS - 基础保姆级教程系列一

CSS盒子模型 <style>.box {width: 200px;height: 100px;padding: 20px;} </style> <div class"box">盒子模型 </div><style>.box {width: 200px;height: 100px;padding: 20px;box-sizing: border-box;} </style> <div class&…

道品科技水肥一体化如何确定灌溉需水量呢?

在农业生产进程之中&#xff0c;持续攀升的生产成本&#xff0c;使农民苦不堪言。其一&#xff0c;水肥用量递增&#xff0c;致使成本上扬&#xff1b;其二&#xff0c;种植成效并不显著&#xff0c;所增经济收益颇为有限。另外&#xff0c;不科学的滴灌施肥亦破坏了农业环境架…

北航软件工程考研难度分析!

C哥专业提供——计软考研院校选择分析专业课备考指南规划 总体情况概述 北航软件工程学硕2024届呈现"稳中有降"态势。2024届复试线335分&#xff0c;较2023届上升25分&#xff0c;但较2022届下降10分。实际录取24人&#xff08;含实验室方向&#xff09;&#xff0c…

网页,app,微信小程序互相跳转

1.网页打开小程序 配置&#xff1a;登录小程序账号&#xff0c;找到账号设置&#xff0c;在基本设置中找到隐私与安全 在明文scheme中点击配置&#xff0c;填写要跳转的小程序页面地址即可 此处只展示一种实现方法&#xff0c;其他参照获取 URL Scheme | 微信开放文档 <a …

SQL,力扣题目1767,寻找没有被执行的任务对【递归】

一、力扣链接 LeetCode_1767 二、题目描述 表&#xff1a;Tasks ------------------------- | Column Name | Type | ------------------------- | task_id | int | | subtasks_count | int | ------------------------- task_id 具有唯一值的列。 ta…

【工具】在线一维码生成器

在国外网站上看到一款条形码生成器&#xff0c;它是开源的&#xff0c;很好用。但是访问慢&#xff0c;也不支持下载一维码&#xff0c; 于是我把他翻译了过来&#xff0c;加上下载条码功能&#xff0c;再加了配色&#xff0c;让界面看上来更丰富 一个可直接使用的工具&#x…

PHM技术应用:发电机线棒高温预警

目录 1 案例背景 1.1 事件描述 1.2 设备概况 1.3 事件过程 2 系统动力学模型 典型工况 故障树 潜在业务提升 3 异常预警规则模型 4 故障排查逻辑 5 小结 1 案例背景 1.1 事件描述 某发电厂的某台发电机组&#xff0c;在满功率工况下&#xff0c;因发电机下层线棒温…

Spark on YARN:Spark集群模式之Yarn模式的原理、搭建与实践

Spark 的介绍与搭建&#xff1a;从理论到实践-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 目录 一、Spark on YARN 的优势 &#xff08;一&#…

是时候用开源降低AI落地门槛了

过去三十多年&#xff0c;从Linux到KVM&#xff0c;从OpenStack到Kubernetes&#xff0c;IT领域众多关键技术都来自开源。开源技术不仅大幅降低了IT成本&#xff0c;也降低了企业技术创新的门槛。 那么&#xff0c;在生成式AI时代&#xff0c;开源能够为AI带来什么&#xff1f;…

机器学习—矩阵乘法的规则

有一个23的矩阵A&#xff0c;有两行三列&#xff0c;把这个矩阵的列想象成三个向量a1,a2,a3&#xff0c;用一个转置&#xff0c;把它相乘&#xff0c;首先&#xff0c;什么是转置&#xff0c;把一个矩阵进行行变列&#xff0c;列变行的操作&#xff0c;所以这些行现在是一个转置…

字节面试Java基础部分——HashMap

字节面试Java基础部分 面试管&#xff1a;Java应该很熟悉吧&#xff0c;接下来问你几个Java基础问题&#xff1a; HashMap 是什么样的数据结构 JDK 7 中&#xff0c;HashMap 由“数组链表”组成&#xff0c;数组是 HashMap 的主体&#xff0c;链表则是主要为了解决哈希冲突而…

Rust 力扣 - 2461. 长度为 K 子数组中的最大和

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历长度为k的窗口&#xff0c;用一个哈希表记录窗口内的所有元素&#xff08;用来对窗口内元素去重&#xff09;&#xff0c;我们取哈希表中元素数量等于k的窗口总和的最大值 题解代码 use std::collecti…