鸿蒙多线程开发——Worker多线程

1、概 述

1.1、基本介绍

Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与主线程分离,在后台线程中运行一个脚本进行耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞主线程的运行。

创建Worker的线程称为宿主线程(不一定是主线程,工作线程也支持创建Worker子线程),Worker自身的线程称为Worker子线程(或Actor线程、工作线程)。每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等,因此每个Worker启动存在一定的内存开销,需要限制Worker的子线程数量。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。示意图如下:

图片

1.2、注意事项

  • 创建Worker时,有手动和自动两种创建方式,手动创建Worker线程目录及文件时,还需同步进行相关配置。

👉🏻 手动创建:开发者手动创建相关目录及文件,此时需要配置build-profile.json5的相关字段信息,Worker线程文件才能确保被打包到应用中。配置代码如下:

"buildOption": {  "sourceOption": {    "workers": [      "./src/main/ets/workers/worker.ets"    ]  }}

👉🏻 自动创建:DevEco Studio支持一键生成Worker,在对应的{moduleName}目录下任意位置,点击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息,无需再手动在build-profile.json5中进行相关配置。

  • 使用Worker能力时,构造函数中传入的Worker线程文件的路径在不同版本有不同的规则。示例如下(不同场景中,加载的url路径规则有所不同,细则看后文【1.3、Woker文件路径规则】)

// 导入模块import { worker } from '@kit.ArkTS';// API 9及之后版本使用:const worker1: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/MyWorker.ets');// API 8及之前版本使用:const worker2: worker.Worker = new worker.Worker('entry/ets/workers/MyWorker.ets');
  • Worker创建后需要手动管理生命周期,且最多同时运行的Worker子线程数量为64个。

    • Worker的创建和销毁耗费性能,建议我们合理管理已创建的Worker并重复使用。Worker空闲时也会一直运行,因此当不需要Worker时,可以调用terminate()接口或close()方法主动销毁Worker。若Worker处于已销毁或正在销毁等非运行状态时,调用其功能接口,会抛出相应的错误。

    • Worker的数量由内存管理策略决定,设定的内存阈值为1.5GB和设备物理内存的60%中的较小者。在内存允许的情况下,系统最多可以同时运行64个Worker。如果尝试创建的Worker数量超出这一上限,系统将抛出错误:“Worker initialization failure, the number of workers exceeds the maximum.”。实际运行的Worker数量会根据当前内存使用情况动态调整。一旦所有Worker和主线程的累积内存占用超过了设定的阈值,系统将触发内存溢出(OOM)错误,导致应用程序崩溃。

  • 由于不同线程中上下文对象是不同的,因此Worker线程只能使用线程安全的库(例如UI相关的非线程安全库不能使用)

  • 序列化传输的数据量大小限制为16MB。

  • 使用Worker模块时,需要在主线程中注册onerror接口,否则当Worker线程出现异常时会发生jscrash问题。

  • 不支持跨HAP使用Worker线程文件。

  • 创建Worker对象时仅允许加载本模块下存在的Worker线程文件,不支持加载其他模块的Worker线程文件。若依赖其他模块提供的Worker功能,需要将Worker实现的整套逻辑封装到方法中,将方法导出后供其他模块使用。

  • 引用HAR/HSP前,需要先配置对HAR/HSP的依赖。

  • 不支持在Worker工作线程中使用AppStorage。

1.3、Woker文件路径规则

构造函数中的scriptURL要求如下:

  • scriptURL的组成包含 {moduleName}/ets 和相对路径 relativePath。

  • relativePath是Worker线程文件相对于"{moduleName}/src/main/ets/"目录的相对路径。

👉🏻 场景1:加载Ability中的Worker线程文件

加载Ability中的worker线程文件,加载路径规则:{moduleName}/ets/{relativePath}。​​​​​​​

import { worker } from '@kit.ArkTS';// worker线程文件所在路径:"entry/src/main/ets/workers/worker.ets"const workerStage1: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/worker.ets');// worker线程文件所在路径:"phone/src/main/ets/ThreadFile/workers/worker.ets"const workerStage2: worker.ThreadWorker = new worker.ThreadWorker('phone/ets/ThreadFile/workers/worker.ets');

👉🏻 场景2:加载HSP中的Worker线程文件

加载HSP中worker线程文件,加载路径规则:{moduleName}/ets/{relativePath}。​​​​​​​

import { worker } from '@kit.ArkTS';// worker线程文件所在路径:"hsp/src/main/ets/workers/worker.ets"const workerStage3: worker.ThreadWorker = new worker.ThreadWorker('hsp/ets/workers/worker.ets');

👉🏻 场景3:加载HSP中的Worker线程文件

加载HAR中worker线程文件存在以下两种情况:

  • @标识路径加载形式:所有种类的模块加载本地HAR中的Worker线程文件,加载路径规则:@{moduleName}/ets/{relativePath}。

  • 相对路径加载形式:本地HAR加载该包内的Worker线程文件,加载路径规则:创建Worker对象所在文件与Worker线程文件的相对路径.

import { worker } from '@kit.ArkTS';// @标识路径加载形式:// worker线程文件所在路径: "har/src/main/ets/workers/worker.ets"const workerStage4: worker.ThreadWorker = new worker.ThreadWorker('@har/ets/workers/worker.ets');// worker线程文件所在路径: "har/src/main/ets/workers/worker.ets"// 创建Worker对象的文件所在路径:"har/src/main/ets/components/mainpage/MainPage.ets"const workerStage5: worker.ThreadWorker = new worker.ThreadWorker('../../workers/worker.ets');

2、案 例

下面将使用一个CPU密集型任务来做案例。

CPU密集型任务是指需要占用系统资源处理大量计算能力的任务,需要长时间运行,这段时间会阻塞线程其它事件的处理,不适宜放在主线程进行。例如图像处理、视频编码、数据分析等。

基于多线程并发机制处理CPU密集型任务可以提高CPU利用率,提升应用程序响应速度。

当任务不需要长时间(3分钟)占据后台线程,而是一个个独立的任务时,推荐使用TaskPool,反之推荐使用Worker。开发步骤如下:

👉🏻 step 1:DevEco Studio提供了Worker创建的模板,新建一个Worker线程,例如命名为“MyWorker”。

图片

👉🏻 step 2:在主线程中通过调用ThreadWorker的constructor()方法创建Worker对象,当前线程为宿主线程。​​​​​​​

// Index.etsimport { worker } from '@kit.ArkTS';const workerInstance: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/MyWorker.ts');

👉🏻 step 3: 在宿主线程中通过调用onmessage()方法接收Worker线程发送过来的消息,并通过调用postMessage()方法向Worker线程发送消息。

例如向Worker线程发送训练和预测的消息,同时接收Worker线程发送回来的消息。​​​​​​​

// Index.etslet done = false;// 接收Worker子线程的结果workerInstance.onmessage = (() => {  console.info('MyWorker.ts onmessage');  if (!done) {    workerInstance.postMessage({ 'type': 1, 'value': 0 });    done = true;  }})workerInstance.onerror = (() => {  // 接收Worker子线程的错误信息})// 向Worker子线程发送训练消息workerInstance.postMessage({ 'type': 0 });

👉🏻 step 4: 在MyWorker.ts文件中绑定Worker对象,当前线程为Worker线程。​​​​​​​

// MyWorker.tsimport { worker, ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@kit.ArkTS';let workerPort: ThreadWorkerGlobalScope = worker.workerPort;

👉🏻 step 5:在Worker线程中通过调用onmessage()方法接收宿主线程发送的消息内容,并通过调用postMessage()方法向宿主线程发送消息。

例如在Worker线程中定义预测模型及其训练过程,同时与主线程进行信息交互。​​​​​​​

// MyWorker.ts// 定义训练模型及结果let result: Array<number>;// 定义预测函数function predict(x: number): number { return result[x];}// 定义优化器训练过程function optimize(): void { result = [0];}// Worker线程的onmessage逻辑workerPort.onmessage = (e: MessageEvents): void => { // 根据传输的数据的type选择进行操作 switch (e.data.type as number) {  case 0:  // 进行训练   optimize();  // 训练之后发送主线程训练成功的消息   workerPort.postMessage({ type: 'message', value: 'train success.' });   break;  case 1:  // 执行预测   const output: number = predict(e.data.value as number);  // 发送主线程预测的结果   workerPort.postMessage({ type: 'predict', value: output });   break;  default:   workerPort.postMessage({ type: 'message', value: 'send message is invalid' });   break; }}

👉🏻 step 6:在Worker线程中完成任务之后,执行Worker线程销毁操作。销毁线程的方式主要有两种:根据需要可以在宿主线程中对Worker线程进行销毁;也可以在Worker线程中主动销毁Worker线程。

在宿主线程中通过调用onexit()方法定义Worker线程销毁后的处理逻辑。​​​​​​​

// Worker线程销毁后,执行onexit回调方法workerInstance.onexit = (): void => { console.info("main thread terminate");}// 方式一:在宿主线程中通过调用terminate()方法销毁Worker线程,并终止Worker接收消息。// 销毁Worker线程workerInstance.terminate();// 方式二:在Worker线程中通过调用close()方法主动销毁Worker线程,并终止Worker接收消息。// 销毁线程workerPort.close();

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python实现SSA智能麻雀搜索算法优化BP神经网络回归模型(优化权重和阈值)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 随着人工智能技术的发展&#xff0c;机器学习算法在各个领域的应用越来越广泛。其中&#xff0c;神…

qt QListView详解

1、概述 QListView 是 Qt 框架中的一个视图类&#xff0c;用于展示模型中的数据。它基于 QAbstractItemView&#xff0c;支持多种视图模式&#xff0c;如列表视图&#xff08;List View&#xff09;、图标视图&#xff08;Icon View&#xff09;等。QListView 是模型/视图框架…

【MySQL】数据库整合攻略 :表操作技巧与详解

前言&#xff1a;本节内容讲述表的操作&#xff0c; 对表结构的操作。 是对表结构中的字段的增删查改以及表本身的创建以及删除。 ps&#xff1a;本节内容本节内容适合安装了MySQL的友友们进行观看&#xff0c; 实操更有利于记住哦。 目录 创建表 查看表结构 修改表结构 …

图片搜索引擎,来快速实现一个高性能的本地图片搜索引擎

文章来自&#xff1a;苏洋博客 以及基于 Redis 来快速实现一个高性能的本地图片搜索引擎&#xff0c;在本地环境中&#xff0c;使用最慢的稠密向量检索方式来在一张万图片中查找你想要的图片&#xff0c;总花费时间都不到十分之一秒。 写在前面 Redis, 你这浓眉大眼的家伙也正…

DevOps-课堂笔记

各种 aaS 类比于计算机网络的 OSI 参考模型&#xff0c;一个软件应用项目需要不同的支撑层&#xff0c;例如从下至上大概需要&#xff1a; 硬件层面的服务器针对硬件做弹性分配的虚拟化机制&#xff0c;例如虚拟机在虚拟化环境内运行的 OS支撑软件应用的中间件&#xff0c;例…

边缘计算的学习

文章目录 概要何为边缘计算&#xff1f;现阶段&#xff0c;企业使用边缘计算相对云计算 整体架构流程边缘网络组件边缘计算与云安全 研究方向结合引用 概要 edge 何为边缘计算&#xff1f; 边缘计算&#xff08;英语&#xff1a;Edge computing&#xff09;&#xff0c;是一种…

Spring Boot实现文件上传与OSS集成:从基础到应用

目录 前言1. 文件上传的基础实现1.1 前端文件上传请求1.2 后端文件接收与保存 2. 集成第三方OSS服务2.1 准备工作2.2 编写OSS集成代码2.3 修改Controller实现文件上传至OSS 3. 文件上传的扩展&#xff1a;多文件上传与权限控制结语 前言 随着互联网应用的快速发展&#xff0c;…

万字长文解读深度学习——卷积神经网络CNN

推荐阅读&#xff1a; 卷积神经网络&#xff08;CNN&#xff09;详细介绍及其原理详解 CNN笔记&#xff1a;通俗理解卷积神经网络 文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;主要组件输入层卷积层 (Convolutional Layer)批归一化层&#xff08;Batch Normalizat…

07 Oracle数据库恢复基础解析:从检查点到归档,一步步构建数据安全防线

文章目录 Oracle数据库恢复基础解析&#xff1a;从检查点到归档&#xff0c;一步步构建数据安全防线一、检查点&#xff08;Checkpoint&#xff09;1.1 检查点定义1.2 检查点重要性1.3 检查点工作原理1.4 手动触发检查点 二、日志&#xff08;Redo Log&#xff09;2.1 日志定义…

css | padding vs margin

前置知识 height是作用域内容(content)区域的 padding和margin用百分比的时候是怎么算的&#xff1f;父元素的宽度。注意&#xff0c;不是根据父元素相应的属性&#xff0c;就是父亲的width 自身的height是0 以下代码&#xff0c;外面盒子是100x10的&#xff0c;里面的widt…

Linux平台C99与C++11获取系统时间

源码: #include <iostream> #include <chrono> #include <ctime> #include <thread>using namespace std; int main() {cout << "===使用C99方式获取系统时间===" << endl;time_t now = time(nullptr);struct tm *tm_c99 = lo…

pwn学习笔记(11)--off_by_one

pwn学习笔记&#xff08;11&#xff09;–off_by_one ​ 在处理for循环或者while循环的时候&#xff0c;有的可能会遇到如下情况&#xff1a; #include<stdio.h>int main(){char buf[0x10];for (int i 0 ; i < 0x10 ; i ){buf[i] getchar();}puts(buf);}​ 多次输…

基于Java Web的传智播客crm企业管理系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

【Vue】Vue3.0(十七)Vue 3.0中Pinia的深度使用指南(基于setup语法糖)

上篇文章&#xff1a; 【Vue】Vue3.0&#xff08;十一&#xff09;Vue 3.0 中 computed 计算属性概念、使用及示例 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月10日15点23分 文章…

常用的c++新特性-->day03

断言和异常 断言断言的基本使用 静态断言静态断言的基本使用 异常异常基本使用c98异常案例 noexceptnoexcept简单案例 断言 断言的基本使用 #include <iostream> #include <cassert>// >>>>>>>>>>>>>>>> 断言的…

11月7日星期四今日早报简报微语报早读

11月7日星期四&#xff0c;农历十月初七&#xff0c;早报#微语早读。 1、河南&#xff1a;旅行社组织1000人次境外游客在豫住宿2夜以上&#xff0c;可申请激励奖补&#xff1b; 2、主播宣称下播后商品恢复原价构成欺诈&#xff0c;广州市监&#xff1a;罚款5万元&#xff1b;…

数据结构-并查集专题(1)

一、前言 因为要开始准备年底的校赛和明年年初的ACM、蓝桥杯、天梯赛&#xff0c;于是开始按专题梳理一下对应的知识点&#xff0c;先从简单入门又值得记录的内容开始&#xff0c;并查集首当其冲。 二、我的模板 虽然说是借用了jiangly鸽鸽的板子&#xff0c;但是自己也小做…

【dvwa靶场:XSS系列】XSS (Stored)低-中-高级别,通关啦

更改name的文本数量限制大小&#xff0c; 其他我们只在name中进行操作 【除了低级可以在message中进行操作】 一、低级low <script>alert("假客套")</script> 二、中级middle 过滤了小写&#xff0c;咱们可以大写 <Script>alert("假客套…

【spark面试】spark的shuffle过程

概述 所有的shuffle的过程本质上就是一个task将内存中的数据写入磁盘&#xff0c;然后另一个task将磁盘中的数据读入内存的过程。 对于mapreduce来说&#xff0c;我们将内存中的数据写入磁盘成为maptask&#xff0c;将磁盘中的数据读入内存称为reducetask。 而对于spark来说&…

MySQL —— Innodb 索引数据结构

文章目录 不用平衡二叉树或红黑树作为索引B树适合作为索引比B树更适合作为索引的结构——B树总结 MySQL 使用 B树索引数据结构&#xff08;因为默认使用 innodb 存储引擎&#xff09; B树&#xff1a;有序数组 平衡多叉树&#xff1b;B树&#xff1a;有序数组链表 平衡多叉树…