鸿蒙多线程开发——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();

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【大数据学习 | kafka】消费者的分区分配规则

1. 概述 上面我们提到过&#xff0c;消费者有的时候会少于或者多于分区的个数&#xff0c;那么如果消费者少了有的消费者要消费多个分区的数据&#xff0c;如果消费者多了&#xff0c;有的消费者就可能没有分区的数据消费。 那么这个关系是如何分配的呢&#xff1f; 现在我们…

Python接口自动化测试自学指南(项目实战)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 接口自动化测试是指通过编写程序来模拟用户的行为&#xff0c;对接口进行自动化测试。Python是一种流行的编程语言&#xff0c;它在接口自动化测试中得到了广…

Redis - 哨兵(Sentinel)

Redis 的主从复制模式下&#xff0c;⼀旦主节点由于故障不能提供服务&#xff0c;需要⼈⼯进⾏主从切换&#xff0c;同时⼤量 的客⼾端需要被通知切换到新的主节点上&#xff0c;对于上了⼀定规模的应⽤来说&#xff0c;这种⽅案是⽆法接受的&#xff0c; 于是Redis从2.8开始提…

24年配置CUDA12.4,Pytorch2.5.1,CUDAnn9.5运行环境

没什么好介绍的&#xff0c;直接说了。 下载 首先打开命令行&#xff0c;输入代码查看显卡最高支持的cuda版本&#xff0c;下载的版本不要高于该版本 nvidia-smi PyTorch 插件这个是PyTorch下载地址&#xff0c;就按照我这么选CUDA版本就选最新的&#xff0c;看好绿框里的CU…

debian系统安装qt的时候 显示xcb相关文件缺失

如果是安装之后的问题 我们可以选择使用ldd的命令查看当前依赖的so那些文件确实 ldd /home/yinsir/Qt/5.15.2/gcc_64/plugins/platforms/libqxcb.so 本人在进行打包的时候 出现则会个报错 ERROR: ldd outputLine: “libxcb-util.so.1 > not found” ERROR: for binary: “/…

找工作就上万码优才,海量技术岗位等你来

已至岁末&#xff0c;不论你将实习&#xff0c;或正在求职&#xff0c;求职平台千千万万&#xff0c;但简历如落叶般无人问津。 是否因未找到理想职位而心生焦虑&#xff1f;别急&#xff0c;万码优才在这里&#xff0c;为你点亮职业之路的明灯&#xff01; 今天给大家推荐一…

⭐SmartControl: Enhancing ControlNet for Handling Rough Visual Conditions

目录 0 Abstract 1 Motivation 2 Related Work 2.1 Text-to-Image Diffusion Model 2.2 Controllable Text-to-Image Generation 2.3 ControlNet 2.4 Control Scale Exploration 3 Method 3.1 Framework 3.2 Control Scale Predictor 3.3 Unaligned Data Constructi…

vue3 + element-plus 的 upload + axios + django 文件上传并保存

之前在网上搜了好多教程&#xff0c;一直没有找到合适自己的&#xff0c;要么只有前端部分没有后端&#xff0c;要么就是写的不是很明白。所以还得靠自己摸索出来后&#xff0c;来此记录一下整个过程。 其实就是不要用默认的 action&#xff0c;要手动实现上传方式 http-reque…

更改Ubuntu22.04锁屏壁纸

更改Ubuntu22.04锁屏壁纸 sudo apt install gnome-shell-extensions gnome-shell-extension-manager安装Gnome Shell 扩展管理器后&#xff0c;打开“扩展管理器”并使用搜索栏找到“锁屏背景”扩展

SDL打开YUV视频

文章目录 问题1&#xff1a;如何控制帧率&#xff1f;问题2&#xff1a;如何触发退出事件&#xff1f;问题3&#xff1a;如何实时调整视频窗口的大小问题4&#xff1a;YUV如何一次读取一帧的数据&#xff1f; 问题1&#xff1a;如何控制帧率&#xff1f; 单独用一个子线程给主线…

SQL server 中 CROSS APPLY的使用

CROSS APPLY 是 SQL Server 中的一个操作符&#xff0c;用于将一个表表达式&#xff08;如子查询、函数等&#xff09;与外部表进行连接。CROSS APPLY 类似于 INNER JOIN&#xff0c;但它允许你在一个查询中多次引用外部表的行&#xff0c;并且可以动态地生成结果集。 基本语法…

【算法】Floyd多源最短路径算法

目录 一、概念 二、思路 三、代码 一、概念 在前面的学习中&#xff0c;我们已经接触了Dijkstra、Bellman-Ford等单源最短路径算法。但首先我们要知道何为单源最短路径&#xff0c;何为多源最短路径 单源最短路径&#xff1a;从图中选取一点&#xff0c;求这个点到图中其他…

纯C++信号槽使用Demo (sigslot 库使用)

sigslot 库与QT的信号槽一样&#xff0c;通过发送信号&#xff0c;触发槽函数&#xff0c;信号槽不是QT的专利&#xff0c;早在2002年国外的一小哥用C写了sigslot 库&#xff0c;简单易用&#xff1b; 该库的官网&#xff08;喜欢阅读的小伙伴可以仔细研究&#xff09;&#xf…

【路径规划】PID搜索算法PSA求解UAV路径规划

摘要 本文研究了基于PID搜索算法&#xff08;PID Search Algorithm, PSA&#xff09;求解无人机&#xff08;UAV&#xff09;路径规划问题。通过引入PID控制思想来控制路径生成过程&#xff0c;使得无人机可以避开障碍物并在复杂地形中寻找最优路径。实验结果表明&#xff0c;…

【大数据学习 | kafka高级部分】kafka的数据同步和数据均衡

1. 数据同步 通过上图我们发现每个分区的数据都不一样&#xff0c;但是三个分区对外的数据却是一致的 这个时候如果第二个副本宕机了 但是如果是leader副本宕机了会发生什么呢&#xff1f; 2. 数据均衡 在线上程序运行的时候&#xff0c;有的时候因为上面副本的损坏&#xff…

java:使用Multi-Release Jar改造Java 1.7项目增加module-info.class以全面合规Java 9模块化规范

common-java是一个我维护了好多年的一个基础项目,编译目标为Java 1.7 现在整个团队的项目要做Java 9以上的技术迁移准备,就需要对这个在内部各项目中被广泛引用的基础项目进行改造,以适合Java 9的模块化规范。 Automatic-Module-Name Java 9的模块化规范(即Java Platform Mod…

机器视觉基础—双目相机

机器视觉基础—双目相机与立体视觉 双目相机概念与测量原理 我们多视几何的基础就在于是需要不同的相机拍摄的同一个物体的视场是由重合的区域的。通过下面的这种几何模型的目的是要得到估计物体的长度&#xff0c;或者说是离这个相机的距离。&#xff08;深度信息&#xff09…

C++继承(图文非常详细)

继承的概念 1.什么是继承 1.简单定义 我们来看一下下面这串代码注意其中的两个类father 和 son using namespace std; #include<iostream> class father { public:void definity(){cout << "father" << endl;} protected:int tall 180;int age …

【机器学习】均方误差根(RMSE:Root Mean Squared Error)

均方误差根&#xff08;Root Mean Squared Error&#xff0c;RMSE&#xff09;是机器学习和统计学中常用的误差度量指标&#xff0c;用于评估预测值与真实值之间的差异。它通常用于回归模型的评价&#xff0c;以衡量模型的预测精度。 RMSE的定义与公式 给定预测值 和实际值 …

Pandas | 数据分析时将特定列转换为数字类型 float64 或 int64的方法

类型转换 传统方法astype使用value_counts统计通过apply替换并使用astype转换 pd.to_numericx对连续变量进行转化⭐参数&#xff1a;返回值&#xff1a;示例代码&#xff1a; isnull不会检查空字符串 数据准备 有一组数据信息如下&#xff0c;其中主要将TotalCharges、MonthlyC…