Harmony ArkTS 并发编程全攻略:从基础概念到多线程实战,彻底搞懂性能优化

51 阅读40分钟

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

1. 计算机基础概念

在日常的生活或工作中,计算机几乎无处不在,特别是对于我们高级开发攻城狮来说、每天都要使用、来开发各种各样的程序,所以为了能够写出更好的程序、了解计算机基础知识是必要的,接下来就让我们来聊聊现代计算机的组成架构和各部分的工作原理。

编辑

编辑

一台计算机,至少由下述几个部分组成:主板、CPU、显卡、硬盘、内存等,如上图。

那么什么是主板、CPU、显卡、内存,他们之间又有什么关联呢?

编辑

带着这些疑惑,我们一起接着往下看

主板:负责连接其他设备,比如CPU、内存、硬盘等,就像我们人体的躯干,包含了人的各种器官。

编辑

CPU:计算机中的最重要的一个硬件,全称为中央处理器(Cntral Processing Uit),是计算机运算核心控制核心。人靠大脑思考,计算机靠CPU来进行 运算并控制 计算机的其他硬件协同工作。 单核一个脑瓜子、8核8个脑瓜子

编辑

编辑

显卡:是计算机中的一个重要硬件组件,主要负责将计算机系统中的数据转换为显示器可以理解的图像信号,从而显示图像和视频内容。相当于电脑的眼睛,一般CPU会集成显卡,对于日常的需求可以应付。

编辑

硬盘(永久大仓库):负责存储各种各样的数据和程序,具有断电数据不丢失的特点。

编辑

内存(临时小仓库) :1-缓存系统中临时数据,2-负责硬盘等硬件上得数据与CPU之间的数据交换过程,3-断点后数据丢失

1569710623185

编辑

关于身体躯干/主板、大脑/CPU、眼睛/显卡、硬盘/大仓库、内存/小仓库之间的关系

当我们在电脑上双击打开QQ时,其实是通过鼠标向CPU发送一条指令,CPU接到指令后,把QQ程序从硬盘加载到内存中,加载完成后,CPU开始执行QQ程序。执行完成后,CPU可以让QQ程序显示在显示器上。这就是程序的一般运行过程,也是上述之间的关系。

思考1:为什么CPU不直接在硬盘里执行程序? 回答1:内存读写速度 > 固态硬盘 > 机械硬盘。 思考2:既然内存这么香,以后都存在内存中不就完事了,他们有什么区别? 回答2:1-内存价格贵、2-数据重启丢失

小总结

主板:人的躯干,连接各个器官;

CPU:人的大脑,负责输入/输出等指令处理;

显卡:人的眼睛,负责在显示屏上显示信息;

硬盘:永久大仓库,例如存放安装的程序、和存储的文档资料;

内存:临时小仓库,例如系统临时数据、或者后端数据库数据来优化项目代表Redis数据库,还有咱JS变量定义的数据都在这里面;

多学1招:装系统、电脑密码忘了 你可以买一个U盘 格式化为系统盘 术语 u盘启动盘制作工具(电脑店、it天空大白菜)

多学2招:如何选电脑 cpu、显卡、内容、硬盘(系统盘是固态硬盘开机快)

2. 同步/异步

铺垫:🤔下述代码的执行结果

console.log(1)     
setTimeout(() =>console.log(2)
}, 2000)
console.log(3)
​
// 132console.log(1)     
setTimeout(() =>console.log(2)
}, 0)
console.log(3)
​
// 132   对
// 123? 
// 124 瑕疵的代表

编辑

  • 原因:代码是从上向下挨个执行,然后执行的过程中不一定是先执行的先打印

在js中,代码从上向下执行,遇到 ajax、setTimeout/setInterval、Promise.then等【耗费时间】的代码,

会加入到浏览器的队列中排队,

等【当前后续代码执行完毕】再从队列中取出来执行

多学一招:被加入浏览器队列的代码称之为异步代码,例如 ajax、setTimeout/setInterval、Promise.then等等,反之同步代码

  • 概念

异步代码:

说法1:会被加入到浏览器队列的代码称之为异步代码,例如 ajax、setTimeout/setInterval、Promise.then 等等

说法2:不按书写✍🏻顺序执行打印结果的代码

同步代码:按照书写顺序执行打印的代码

编辑

同步异步谁的执行效率高

  • 经典代码1
<script>
console.log(1)         
console.log(2)      
console.log(3)      
               
​

setTimeout(() => {
console.log(2)
}, 2000)
console.log(1)   
console.log(3)
</script>
  • 经典代码2
<script>
console.log('1 异步请求前得逻辑')
​
const xhr = new XMLHttpRequest
xhr.onreadystatechange = function() 
{
if (xhr.readyState == 4)
{
  if (xhr.status == 200)
  {
    res = JSON.parse(xhr.responseText)
    console.log('2 异步请求拿到数据后的逻辑', res)
  } else {
    alert(xhr.status)
  }
}
}
// xhr.open(请求类型get/post,url,默认true异步也可以改成false同步)
xhr.open('get', `http://www.baidu.com/sugrec?prod=pc&wd=html`) 
xhr.send(null)
​
​
// console.log('3 其他逻辑', res) // 切记:虽然上面定义了res 但是不能用 因为是异步请求
console.log('3 其他逻辑') 
</script>


经典代码3

console.log(1)
​
setTimeout(() => {
console.log('setTimeout')
}, 1000)
​
​
new Promise((resolve) =>// 立即执行 同步
  console.log(2)
  resolve();
})
.then(() => { // 异步 可以理解为耗费时间的
  console.log(3)
})
​
console.log(4)   
​
1 
2
4
3
setTimeout
  • 经典代码4
console.log(1)
​
setTimeout(() => {
console.log('setTimeout')
}, 1000)
​
​
new Promise((resolve) =>// 立即执行 同步
  console.log(2)
  resolve();
})
.then(() => { // 异步 可以理解为耗费时间的
  console.log(3)
})
​
new Promise((resolve) => { 
  resolve();
})
.then(() => {
  console.log('a')
})
​
new Promise((resolve) => { 
  resolve();
})
.then(() => {
  console.log('b')
})
​
​
console.log(4)   
​
​
1 
2
4
3
a
b
setTimeout
  • 经典代码5
console.log(1);
async function async1() {
console.log("async1 start");
await 666;   // 细节:await同行的立马执行,后续的全部加到微任务里面
console.log("async1 end");
console.log("async2 end");
console.log("async3 end");
}
async1();
console.log(2);
  • 经典代码6:字节面试题
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
//Promise.resolve( async2())
//.then(res => {
//console.log('async1 end')
//})
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout') 
},0)  
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
​
​
// script start
// async1 start
// async2
// promise1
// async1 end
// promise2
// setTimeout

小总结

同步:代码按照顺序从上到下挨个执行

异步:不按顺序执行

宏任务/微任务:setTimeout/setInterval、Promise.then/catch

校验是否真正理解了:经典代码4、经典代码6

3. 串行/并发/并行

  • 并发(Concurrency) :多个任务在同一时间段内切换执行,但不一定同时进行,注重任务管理。(时间片快速切换执行 视觉达到一个提升同事处理能力)

编辑

  • ​​​​​​并行(Parallelism) :多个任务真正同时执行,通常在多核处理器上进行,注重同时处理能力。
  • 代码角度:串行-同步执行代码

编辑

4. CPU/IO密集计算

  • CPU密集型任务

是指在执行过程中需要大量CPU资源的任务。这类任务主要消耗计算资源,而不是依赖于外部的输入/输出操作。通常情况下,CPU密集型任务会在执行时占用较多的CPU时间,而不会涉及大量的等待外部数据的时间。

以下是一些常见的CPU密集型任务的例子:

数学计算:大规模的数学运算,比如矩阵运算、复杂的数值计算等。 图像处理:如图像渲染、图像特征提取、图像识别等需要大量计算的任务。 加密解密:涉及大量的加密和解密运算的任务,比如数据加密解密、数字签名等。 编译任务:编译大型代码库时会产生大量的计算密集型任务。 模拟和建模:例如科学计算领域的大规模模拟和建模任务,比如天气模拟、流体动力学模拟等。 在处理CPU密集型任务时,通常需要充分利用计算资源,如多核CPU、GPU等,以提高任务的处理速度和效率。优化算法、并行计算、异步编程等技术也常常会被应用于处理CPU密集型任务,以最大程度地利用系统的计算资源。

  • I/O密集型任务

单词含义:I input 输入、 O output 输出 用户角度IO操作:鼠标键盘-是计算机输入信息,显示器-是输出设备 电脑角度IO操作:CPU、内存与其他设备之间数据转移过程就是IO操作,例如数据从磁盘读到内存,或者内存写到磁盘 编程角度IO操作:进程读取数据操作

是指那些主要瓶颈在于输入输出(Input/Output,简称IO)操作而非计算操作的计算机任务。这类任务的特点是其核心工作在于与外部设备(如硬盘、网络接口等)进行数据交换,而非大量的CPU计算。在执行过程中,相对于CPU的计算时间,程序更频繁地处于等待IO操作完成的状态。

  • 主要活动: 磁盘IO:包括读取或写入文件、数据库查询、数据备份恢复等涉及硬盘数据存取的操作。 网络IO:如发送和接收网络请求(HTTP、FTP、TCP/IP等)、进行远程API调用、数据同步、流媒体传输等。 设备交互:与打印机、扫描仪、摄像头等外设进行数据通信。 操作系统交互:如系统调用、信号处理、消息队列等。
  • 特点: 等待时间显著:由于硬件设备的物理限制,尤其是磁盘和网络的访问速度远低于CPU的处理速度,程序在执行IO操作时通常会经历较长的等待时间,导致CPU在大部分时间内处于空闲状态。 计算相对较少:相较于CPU密集型任务,IO密集型任务的计算需求较小,即使有计算,其复杂度和耗时通常远不及IO操作本身。 并行潜力:由于IO操作期间CPU常常处于等待状态,这类任务往往具有良好的并发性。通过多线程、异步编程或者事件驱动等技术,可以在一个任务等待IO响应的同时,让CPU处理其他任务,从而提高整体系统的吞吐率和响应速度。
  • 典型应用场景: Web服务:处理高并发的HTTP请求,如动态网页生成、API接口响应等,每个请求可能涉及数据库查询、文件读取等IO操作。 数据爬取:使用如requests库抓取网页内容,解析HTML(如使用BeautifulSoup库)等,涉及网络请求和数据解析。 文件处理:大规模的数据导入导出、文件压缩解压、日志分析等,频繁进行磁盘读写。 数据库操作:尤其是涉及大量查询、索引构建、备份恢复等场景。 实时通信:如即时聊天应用、在线游戏的网络数据交换等。
  • 优化策略: 多线程或多进程:利用操作系统提供的并发机制,让多个任务同时进行IO操作,减少整体等待时间。 异步编程:采用非阻塞IO模型,使得在等待IO时不会阻塞主线程,能够处理其他任务。 缓存:利用内存或其他高速存储介质缓存经常访问的数据,减少对慢速设备(如磁盘)的直接访问。 批处理:合并多次小规模IO操作为单次大规模操作,以减少操作次数和上下文切换开销。 硬件升级与优化:使用更快的磁盘阵列、SSD、高速网络设备等,提升IO性能。

结论:实战开发中遇到下述场景,要意识到属于CPU密集任务、I/O密集任务留心性能优化。

常见业务场景具体业务描述CPU密集型I/O密集型
图片/视频编解码将图片或视频进行编解码再展示。
压缩/解压缩对本地压缩包进行解压操作或者对本地文件进行压缩操作。
JSON解析对JSON字符串的序列化和反序列化操作。×
模型运算对数据进行模型运算分析等。×
网络下载密集网络请求下载资源、图片、文件等。×
数据库操作将聊天记录、页面布局信息、音乐列表信息等保存到数据库,或者应用二次启动时,读取数据库展示相关信息。×
  • 小总结

是什么:就是脑瓜子、和磁盘读写头疼 影响性能

有哪些:上述常见业务场景 咱们要注意性能优化 如何解决:下面准备讲的进程线程  

5. 进程线程

概念⭐️

进程】进程是正在执行的程序的实例,它是系统资源分配的基本单位。每个进程都有自己的地址空间、数据栈和其他辅助数据,允许它独立地运行。

  • 资源独立:进程之间相对独立,每个进程有自己的内存空间。
  • 开销大:创建和管理进程的开销相对较大,因为需要分配和管理独立的资源。
  • 隔离性:一个进程的崩溃一般不会影响其他进程。
  • 具象化:  电脑上所有双击打开的软件(有的软件是多进程的 例如QQ、VSCode等等)

编辑

编辑

线程】线程是进程中的一个执行单元,一个进程可以包含一个或多个线程。线程是比进程更小的并发单位,多个线程可以共享同一进程的资源。

  • 共享资源:线程之间可以共享进程的内存以及其他资源,这使得线程间通信更加高效。
  • 开销小:线程的创建和管理开销相对较小,能有效利用系统资源。
  • 并发执行:多个线程可以同时执行,提高程序的并发性和性能。
  • 具象化: 比如QQ可以同时视频、传输文件、文字聊天,可以使用一个线程负责处理视频,一个线程负责传输文件,一个线程负责文字聊天,让这些任务并发执行。

面试题:谈谈你对进程线程的理解

帅哥:
- 是腿超长的
- 是腰超细的
- 是身材好的有肱二头肌的
​
进程:一个正在运行软件
- 进程是正在执行的程序的实例,它是系统资源分配的基本单位。
- 进程是一个独立的程序运行环境
- 是系统进行【资源分配】的基本单位
​
线程:
- 线程是进程内部的执行单位(对标QQ、谷歌)
- 是操作系统能够进行运算【调度】的最小单位。
​
​
​
谷歌浏览器:多进程多线程架构
浏览器主进程,负责创建和销毁tab进程、负责交互前进后退、负责网页文件下载等
渲染进程:每个tab对应一个渲染进程,下面有GUI渲染线程、JS引擎线程、事件线程、定时器线程、异步请求线程
服务器返回数据 超文本 里面包含了 js、css、html
GUI渲染线程:负责绘制html、css  
JS引擎线程:复制解析js代码 遇到异步代码(也就是耗费时间的会交给其他线程 下面3个)
事件线程、定时器线程、异步请求(其他线程给放到队列中   宏/微)
GPU进程:负责3D图绘制
第三方插件进程:负责第三方插件处理,例如神龙教主跨域、广告拦截插件等



是什么:进程-一个正在运行软件、线程-内部执行单位
为啥学:鸿蒙项目读取数据库文件、下载文件会有大量的I/O密集影响性能 所以避免影响 当前渲染线程解析页面 因此创建新的线程处理 

进程由来

计算机刚出现的时候,是为了解决数学计算的问题,因为很多大量的计算,通过人力去完成是很耗时间和人力成本的。

在最初的时候,计算机只能接受一些特定的指令,用户输入一个指令,计算机就做一个操作。 当用户在思考或者输入数据时,计算机就在等待。显然,这样效率会很低下,因为很多时候,计算机处于等待用户输入的状态。

那么,能不能把一系列需要操作的指令预先写下来,形成一个清单,然后一次性交给计算机,计算机不断地去读取指令来进行相应的操作?

就这样, 批处理操作系统诞生了。用户可以将需要执行的多个程序写在磁带上,然后交由计算机去读取并逐个地执行这些程序,并将输出结果写到另一个磁带上。

虽然批处理操作系统的诞生极大地提高了任务处理的便捷性,但是仍然存在一个很大的问题:假如有两个任务 A 和 B,任务 A 在执行到一半的过程中,需要读取大量的数据输入(I/O操作),而此时CPU 只能静静地等待任务 A 读取完数据才能继续执行,这样就白白浪费了 CPU 资源。

如下图所示:

编辑

人们于是想:

能否在 任务 A 读取数据的过程中,让 任务B 去执行; 当 任务A 读取完数据之后,让 任务B 暂停,然后让 任务A 继续执行?

编辑

但是这样就有一个问题,原来每次都是一个程序在计算机里面运行,也就说内存中始终只有一个程序的运行数据。 而如果想要 任务A 执行 I/O操作 的时候,让 任务B 去执行,必然内存中要装入多个程序,那么如何处理呢?多个程序使用的数据如何进行辨别呢?并且,当一个程序运行暂停后,后面如何恢复到它之前执行的状态呢?

这个时候,人们就发明了进程,用进程来对应一个程序,每个进程对应一定的内存地址空间,并且只能使用它自己的内存空间,各个进程间互不干扰。 并且,进程保存了程序每个时刻的运行状态,这样就为进程切换提供了可能。当进程暂停时,它会保存当前进程的状态(比如进程标识、进程的使用的资源等),在下一次重新切换回来时,便根据之前保存的状态进行恢复,然后继续执行。

正是进程的诞生,让并发成为可能。

进程的定义 进程是一个程序在计算机中执行的实例。它不仅包括程序的代码,还包括程序的当前状态、资源(如内存、CPU时间等)、程序计数器、堆栈和数据等。 进程是操作系统进行资源管理和调度的基本单位。

简单来说,进程就是程序在运行时的【容器】,它包含了程序执行所需要的一切资源和信息。当你启动一个应用程序时,操作系统会为该程序创建一个进程,以便管理和执行。

线程由来

在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。举个具体的例子来说,我们平常用word文档编辑内容的时候,都会有一个自动保存的功能,这个功能的作用是,当计算机出现故障的情况下如果用户未保存文档,则能够恢复到上一次自动保存的点。

假设word的自动保存因为磁盘问题导致写入较慢,势必会影响到用户的文档编辑功能,直到磁盘写入完成用户才可编辑,这种体验是很差的。如果我们把一个进程中的多个任务通过线程的方式进行隔离,那么按照前面提到的进程演进的理论来说,在单核心CPU架构中可以通过CPU的时间片切换实现线程的调度充分利用CPU资源以达到最大的性能。

漫画解读

编辑

鸿蒙进程线程模型⭐️

系统的进程模型如下图所示。

  • 通常情况下,应用中(同一Bundle名称)的所有UIAbility、ServiceExtensionAbility和DataShareExtensionAbility均是运行在同一个独立进程(主进程)中,如下图中绿色部分的“Main Process”。

UIAbility 页面能力 窗口 -> 加载页面

ServiceExtensionAbility 后台服务扩展能力,提供后台运行并对外提供相应能力。

DataShareExtensionAbility 数据共享扩展能力,用于对外提供数据读写服务。

  • 应用中(同一Bundle名称)的所有同一类型ExtensionAbility(除ServiceExtensionAbility和DataShareExtensionAbility外)均是运行在一个独立进程中,如下图中蓝色部分的“FormExtensionAbility Process”、“InputMethodExtensionAbility Process”、其他ExtensionAbility Process。

FormExtensionAbility:卡片扩展能力,提供卡片开发能力。

WorkSchedulerExtensionAbility:延时任务扩展能力,允许应用在系统闲时执行实时性不高的任务。

InputMethodExtensionAbility:输入法扩展能力,用于开发输入法应用。

AccessibilityExtensionAbility:无障碍服务扩展能力,支持访问与操作前台界面。

FileShareExtensionAbility:文件共享扩展能力,用于应用间的文件分享。预留能力,仅系统应用支持。

等等

  • WebView拥有独立的渲染进程,如下图中黄色部分的“Render Process”。

鸿蒙加载h5网页

图1 进程模型示意图

img

编辑

  • Stage模型下的线程主要有如下三类:

    • 主线程

      • 执行UI绘制。
      • 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
      • 管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程。
      • 分发交互事件。
      • 处理应用代码的回调,包括事件处理和生命周期管理。
      • 接收TaskPool以及Worker线程发送的消息。
    • TaskPool Worker线程

      • 用于执行耗时操作,支持设置调度优先级、负载均衡等功能,推荐使用。
    • Worker线程

      • 用于执行耗时操作,支持线程间通信。

        TaskPool与Worker的运作机制、通信手段和使用方法可以参考TaskPool和Worker的对比

编辑

大数据量场景下查询数据可能会导致耗时长甚至应用卡死,如有相关操作可参考文档批量数据写数据库场景

对于需要频繁数据库操作的场景,由于读写数据库存在耗时,因此推荐在子线程中操作,避免阻塞UI线程。⚠️

小总结⭐️

  • 谈谈你对进程线程理解
是什么 (回答面试官的):进程-一个正在运行软件、线程-内部执行单位
​
为啥学(自己看的):鸿蒙项目读取数据库文件、下载文件会有大量的I/O密集影响性能 所以避免影响 当前渲染线程解析页面 因此创建新的线程处理 
  • 鸿蒙有几个进程、线程
3个进程:主进程-创建窗口加载页面等等、扩展进程-、渲染进程-鸿蒙加载h5网页 m.jd.com 实战主要用来加载 用户隐私协议、或者一些活动网页等等
3个线程:主线程-绘制页面/接收TaskPool以及Worker线程发送的消息等等、TaskPool线程-获取数据库数据、Worker线程-自己写 下载

强调目的:老鸟学这个很简单,新手会比较难,所以切记学这个玩意1是为了面试,2是为了让你有意识 后面写项目 遇到加密解密、遇到图片处理、遇到数据库操作、遇到文件上传下载 为了避免影响主线程渲染或者性能 切记要开线程处理。

6. 异步并发 (Promise和async/await)

Promise和async/await提供异步并发能力,是标准的JS异步语法。异步代码会被挂起并在之后继续执行,同一时间只有一段代码执行,适用于单次I/O任务的场景开发,例如一次网络请求、一次文件读写等操作。无需另外启动线程执行。

异步语法是一种编程语言的特性,允许程序在执行某些操作时不必等待其完成,而是可以继续执行其他操作。

Promise是一种用于处理异步操作的对象,可以将异步操作转换为类似于同步操作的风格,以方便代码编写和维护。Promise提供了一个状态机制来管理异步操作的不同阶段,并提供了一些方法来注册回调函数以处理异步操作的成功或失败的结果。

Promise有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。Promise对象创建后处于pending状态,并在异步操作完成后转换为fulfilled或rejected状态。

async/await是一种用于处理异步操作的Promise语法糖,使得编写异步代码变得更加简单和易读。通过使用async关键字声明一个函数为异步函数,并使用await关键字等待Promise的解析(完成或拒绝),以同步的方式编写异步操作的代码。

async函数是一个返回Promise对象的函数,用于表示一个异步操作。在async函数内部,可以使用await关键字等待一个Promise对象的解析,并返回其解析值。如果一个async函数抛出异常,那么该函数返回的Promise对象将被拒绝,并且异常信息会被传递给Promise对象的onRejected()方法。

7. 多线程并发TaskPool

任务池(TaskPool)作用是为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能,且您无需关心线程实例的生命周期。具体接口信息及使用方法详情请见TaskPool

// 同时发生的或者并行的方法
@Concurrent
function printArgs(args: number): number {
  console.info("printArgs: " + args);
  return args;
}
​
// execute执行一个taskpool任务 返回prmoise对象
// 对象里面保存了返回的数据
taskpool.execute(printArgs, 100).then((value: Object) => { // 100: test number
console.info("taskpool result: " + value);
});
​
细节1:封装就是跟咱们之前函数一样只不过加了@Concurrent装饰器
细节2:调用有区别 第一个实参是 线程名、因此 第2个实参赋值给第1个形参、第3个实参赋值给第2个形参
细节3:调用执行完毕返回promise 里面是 线程return的数据
​
或者 
const task: taskpool.Task = new taskpool.Task(任务名, 参数...)
await taskpool.execute(task)

基础使用

创建logThread打印 hello 在aboutToAppear执行这个并发任务

import { taskpool } from '@kit.ArkTS'// 并发任务
@Concurrent function logThread() {
console.log('slj hello logThread')
return 66
}
@Entry
@Component
struct Index {
​
async aboutToAppear() {
  // console.log('slj hello') 假设非常消耗性能 影响UI渲染
  const result = await taskpool.execute(logThread) as number
  console.log('taskpool线程返回结果:', result)
}
​
build() {
  Text('查看打印')
}
}

传参任务

sumThread任务传递的数据+2

import { taskpool } from '@kit.ArkTS'// 并发任务
@Concurrent function sumThread(data:number) {
return data+2
}
@Entry
@Component
struct Index {
​
async aboutToAppear() {
  // this.num+=2 假设非常消耗性能 影响UI渲染
  const result = await taskpool.execute(sumThread, 100) as number
  console.log('taskpool线程返回结果:', result)
}
​
build() {
  Text('查看打印')
}
}

验证价值

aboutToAppear循环打印1-200000000累计求和放到页面num中展示 查看性能

import { taskpool } from '@kit.ArkTS'// 多线程任务
@Concurrent
function sumThread() {
// return data+1
let sum = 0
for (let i=0; i<=200000000; i++) {
  sum += i
}
return sum
}
​
​
@Entry
@Component
struct Index {
​
@State num:number = 0
async aboutToAppear() {
​
  // 同步代码堵塞当前界面渲染,影响用户体验
  // let sum = 0
  // for (let i=0; i<=200000000; i++) {
  //   sum += i
  // }
  // this.num = sum
​
  // 异步并行代码 不影响用户当前操作
  this.num = await taskpool.execute(sumThread) as number
}
​
build() {
  Column() {
    Text('测试:' + this.num)
  }
}
}

取消任务 -

import { taskpool } from '@kit.ArkTS'// 并发任务
@Concurrent function sumThread() {
if (taskpool.Task.isCanceled()) {
  console.info("tslj 任务取消1");
  return
}
​
// 2s sleep
let t: number = Date.now();
while (Date.now() - t < 6000) {
  continue;
}
​
if (taskpool.Task.isCanceled()) {
  console.info("tslj 任务取消2");
  return
}
​
console.log('slj 任务执行')
}
​
@Entry
@Component
struct Test {
@State num:number = 0private task: taskpool.Task | null = nullaboutToAppear() {
  // 执行任务:创建子线程
  this.task = new taskpool.Task(sumThread)
  taskpool.execute(this.task)
}
​
build() {
  Column() {
    Text('num ' + this.num)
    Button('取消').onClick(() => {
        taskpool.cancel(this.task)
    })
  }
}
}

优先级任务 -

import { taskpool } from '@kit.ArkTS'// 并发任务
@Concurrent function logThread(n:number) {
console.log(`任务 ${n}`)
}
​
@Entry
@Component
struct Test {
@State num:number = 0async aboutToAppear() {
  // 同时创建和执行10个任务 指定第10个任务是最高优先级
  for (let index = 1; index <= 10; index++) {
    // 创建任务,传入要执行的函数和该函数的形参
    const task = new taskpool.Task(logThread, index)
    if (index === 10) {
      taskpool.execute(task, taskpool.Priority.HIGH)
    } else {
      // 执行任务,并且制定优先级 taskpool.Priority.LOW
      taskpool.execute(task, taskpool.Priority.LOW)
    }
  }
}
​
build() {
  Column() {
  }
}
}

宿主线程通信

宿主线程‌是指在多线程编程中,负责执行UI绘制、管理引擎实例、分发和处理事件等主要职责的线程。

TaskPool可以直接通过返回结果,将数据传递回来。但是如果,是持续监听的任务,那么此时可以使用 sendData和 onReceiveData 和主线程进行通信

import { taskpool } from '@kit.ArkTS'// 并发任务
@Concurrent
function sumThread() {
// return data+1
let sum = 0
for (let i=0; i<=10; i++) {
  sum += i
  // 假设子线程需要给宿主线程发送通信
  taskpool.Task.sendData(i)
}
return sum
}
​
@Entry
@Component
struct Test {
@State num:number = 0async aboutToAppear() {
  const task: taskpool.Task = new taskpool.Task(sumThread);
​
  // 1. 宿主线程监听到的数据
  task.onReceiveData((result: string) => {
    console.log("宿主线程监听到的数据:", result)
    // 子给父发数据 然后父处理逻辑
  })
​
  // 2. 执行任务
  const result = await taskpool.execute(task)
  console.log("直接返回的结果", result)
​
}
​
build() {
  Text('查看打印')
}
}

实战案例:多线程存储

- 针对于并行改进封装

constructor仅保存数据loadRdbUtil传递context 并且所有并行都要传递

import { relationalStore } from "@kit.ArkData"
import { BusinessError } from "@kit.BasicServicesKit"class RdbUtil {
​
public table: relationalStore.RdbStore | undefined = undefined
private tableName:string = ''
private tableSql:string = ''// loadRdbStore() {}
constructor(tableName:string, tableSql:string) {
  // 一、保存
  this.tableName = tableName
  this.tableSql = tableSql
  // 二、直接用(多线程特殊不能直接用 必须传递context)
}
​
// FIXED: 针对于多线程并行单独处理
async loadRdbUtil(context:Context) {
  if (this.table) return
  // 窗口 this.context
  // 页面 getContext(this)
  const store = await relationalStore.getRdbStore(context, {
    name: 'hm2502.rdb',
    securityLevel: relationalStore.SecurityLevel.S3
  })
  // 1 创建表/架子
  store.executeSql(this.tableSql)
  // 2 保存起来 后期通过这个表管理数据
  this.table = store
}
​
get<T>(columns:Array<string>):Promise<T> { // ['id', 'title', 'content']
  console.log('b')
  return new Promise((resolve, reject) => {
    console.log('c')
    const predicates = new relationalStore.RdbPredicates(this.tableName);
    // predicates.equalTo('id', 1);
​
    console.log('d', this.table)
    this.table?.query(predicates, columns, (err: BusinessError, resultSet) => {
      if (err) {
        console.log('查看错误:', JSON.stringify(err))
        return reject([])
      }
      let temp: object[] = []
      while (resultSet.goToNextRow()) {
        temp.push(resultSet.getRow())
      }
      console.log('打印结果:', JSON.stringify(temp))
      return resolve(temp as T)
    })
  })
}
​
put(data: relationalStore.ValuesBucket):Promise<number> { // number插入的行号 唯一编号
  return new Promise((resolve, reject) => {
    this.table?.insert(this.tableName, data, (err: BusinessError, rowId: number) => {
      if (err) {
        console.log('查看错误:', JSON.stringify(err))
        return reject(0)
      }
      console.log('rowId ', rowId)
      resolve(rowId)
    })
  })
}
​
del(id:number):Promise<number> { // 1-代表删除成功了 影响了1行 因为你就删除1行 0-代表删除失败 例如id=1第一次删除成后 再删除返回的就是0影响0行 因为数据不存在了
  return new Promise((resolve, reject) => {
    const predicates = new relationalStore.RdbPredicates(this.tableName);
    predicates.equalTo('id', id);
    this.table?.delete(predicates, (err: BusinessError, rows: number) => {
      if (err) {
        console.log('查看错误:', JSON.stringify(err))
        return reject(0)
      }
      console.log('rows ', rows)
      resolve(rows)
    })
  })
}
​
}
​
export const collectTable = new RdbUtil('collect', `create table if not exists collect (
id integer primary key autoincrement,
title text,
img text
)`)
export const downloadTable = new RdbUtil('download', `create table if not exists download (
id integer primary key autoincrement,
title text,
content text,
img text
)`)
​
​
​- 使用

a) 封装两个线程 getCollectConcurrent、postCollectConcurrent 把查询、增加代码放进去 只不过多了

await collectTable.loadRdbUtil(context)

b) 把点击事件里面的直接查询,改成触发线程

import { collectTable } from '../utils/RdbUtilConcurrent';
import taskPool from '@ohos.taskpool';
​
interface CollectItemType {
id: number
title: string
img: string
}
​
​
@Concurrent
async function getCollectConcurrent(context:Context) {
await collectTable.loadRdbUtil(context)
return await collectTable.get<CollectItemType[]>(['id', 'title', 'img'])
}
​
@Concurrent
async function postCollectConcurrent(context:Context) {
await collectTable.loadRdbUtil(context)
const state = await collectTable.put({
  title: 'b商品'+Date.now(), // 当前时间戳
  img: 'https://m.360buyimg.com/mobilecms/s750x750_jfs/t1/308912/10/1389/100791/6826e075Fb77d2250/1d72334af76812e0.jpg!q80.dpg.webp'
})
return state
}
​
@Entry
@Component
struct Test {
​
@State list: CollectItemType[] = []
​
build() {
  Column() {
    Button('查').onClick(async () => {
      const result = await taskPool.execute(getCollectConcurrent, getContext(this))
      console.log('查询结果:', JSON.stringify(result) )
      this.list = result as CollectItemType[]
    })
​
    Button('存').onClick(async () => {
      const result = await taskPool.execute(postCollectConcurrent)
      console.log('增加结果:', result )
    })
​
​
    List() {
      ForEach(this.list, (item: CollectItemType) => {
        ListItem() {
          Column() {
            Image(item.img).height(200)
            Text(item.title).maxLines(3).textOverflow({overflow:TextOverflow.Ellipsis}).fontSize(20).margin({top:10,bottom:10})
          }.width('100%').alignItems(HorizontalAlign.Center)
        }
      })
    }.layoutWeight(1)
​
  }
}
}

8. 多线程并发Worker

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

图1 Worker运作机制示意图

img

编辑

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

  • 语法说明

右击【新建】【Worker】 创建线程 Worker1.ets

import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
​
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;workerPort.onmessage = (e: MessageEvents) => {
// =======================
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
写线程做干的事情
// =======================
}
​
/**
workerPort.onmessageerror = (e: MessageEvents) => {
}
​
workerPort.onerror = (e: ErrorEvent) => {
}
  • 页面触发/执行
private workerStage: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/Worker1.ets');

this.workerStage.postMessage({})   触发线程   触发必须传递一个实参 可以写空对象

基础使用

创建在DevEco中鼠标右击创建Worker1.ets线程 打印hello就行

import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
​
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
​
/**
* Defines the event handler to be called when the worker thread receives a message sent by the host thread.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
workerPort.onmessage = (event: MessageEvents) => {
// =============================================
console.log('线程业务逻辑')
// =============================================
};
​
/**
* Defines the event handler to be called when the worker receives a message that cannot be deserialized.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
workerPort.onmessageerror = (event: MessageEvents) => {
};
​
/**
* Defines the event handler to be called when an exception occurs during worker execution.
* The event handler is executed in the worker thread.
*
* @param event error message
*/
workerPort.onerror = (event: ErrorEvent) => {
};
  • 使用
import { worker } from '@kit.ArkTS';
​
@Entry
@Component
struct Index {
private workerStage: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/Worker1.ets');
​
build() {
  Button('执行').onClick(() => {
    this.workerStage.postMessage({})
  })
}
}

宿主线程通信

  • 宿主线程:1-侦听子线程推送数据、2-给子发送数据 a-父通知/执行子 下载文件、c-父收到数据 展示进度
// 监控【子】推送的数据
workerStage.onmessage = (e: MessageEvents) => {
console.log("【子线程】传递的数据: ", e.data)
}
// 给【子】发送数据
workerStage.postMessage({ a:1,b:2 })
子线程 b-执行下载、把下载进度通知父

// 监控【父】推送的数据
workerPort.onmessage = (e: MessageEvents) => {
​
console.log("【父线程】传递的数据: ", e.data)

// 给【父】发送数据
workerPort.postMessage({a:11,b:222}})
 
 
}

简单记 postMessage onmessage

创建在DevEco中鼠标右击创建Worker2.ets线程

import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
​
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
​
/**
* Defines the event handler to be called when the worker thread receives a message sent by the host thread.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
// 切记切记切记 创建的是子线程
// 父监控子传递的数据   1
// 子监控父传递的数据   2 ✅
workerPort.onmessage = (event: MessageEvents) => {
console.log('【父线程】传递数据:', JSON.stringify(event.data))
​
// 子 发 父
workerPort.postMessage({a:11,b:22})
};
​
/**
* Defines the event handler to be called when the worker receives a message that cannot be deserialized.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
workerPort.onmessageerror = (event: MessageEvents) => {
};
​
/**
* Defines the event handler to be called when an exception occurs during worker execution.
* The event handler is executed in the worker thread.
*
* @param event error message
*/
workerPort.onerror = (event: ErrorEvent) => {
};

接着测试

import { MessageEvents, worker } from '@kit.ArkTS';
​
@Entry
@Component
struct Index {
private workerStage: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/Worker2.ets');
​
​
aboutToAppear() {
  this.workerStage.onmessage = (event: MessageEvents) => {
    console.log('【子线程】传递数据:', JSON.stringify(event.data))
  }
}
​
​
build() {
  Button('执行').onClick(() => {
    // 父 发 子
    this.workerStage.postMessage({ a:1,b:2 })
  })
}
}

验证价值

发现问题

@Entry
@Component
struct Index {
@State num:number = 0aboutToAppear() {
  let sum = 0
  for (let i=0; i<=200000000; i++) {
    sum += i
  }
  this.num = sum
}
build() {
  Text('当前数字:' + this.num)
}
}

解决问题:创建Worker3.ets

import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
​
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
​
/**
* Defines the event handler to be called when the worker thread receives a message sent by the host thread.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
workerPort.onmessage = (event: MessageEvents) => {
let sum = 0
for (let i=0; i<=200000000; i++) {
  sum += i
}
// this.num = sum// 没有return 所以得发送给父
workerPort.postMessage(sum)
};
​
/**
* Defines the event handler to be called when the worker receives a message that cannot be deserialized.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
workerPort.onmessageerror = (event: MessageEvents) => {
};
​
/**
* Defines the event handler to be called when an exception occurs during worker execution.
* The event handler is executed in the worker thread.
*
* @param event error message
*/
workerPort.onerror = (event: ErrorEvent) => {
};

测试

import { MessageEvents, worker } from '@kit.ArkTS';
​
@Entry
@Component
struct Index {
@State num:number = 0
​
​
private workerStage: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/Worker3.ets');
// 1 线程对象
// 2 打开页面触发子 让他执行 postMessage({})
// 3 侦听子传递数据 自己处理
aboutToAppear() {
  this.workerStage.onmessage = (event: MessageEvents) => {
    this.num = event.data
  }
  this.workerStage.postMessage({})
}
​
​
build() {
  Text('当前数字:' + this.num)
}
}

综合案例:文件上传下载

下次有机会给大家分享,也可以私聊小编付费加急 嘿嘿

9. 线程通信

a)汇总

通信场景能力支持
同Ability通信Emitter、EventHub、CommonEvent
跨Ability通信Emitter、EventHub、CommonEvent
跨线程通信Emitter、CommonEvent、Worker、Taskpool
跨进程通信CommonEvent、IPC&RPC

b)EventHub

developer.huawei.com/consumer/cn…

@CustomDialog
export struct LoadingDialog {
controller?: CustomDialogController
​
@Prop message: string = ''
​
​
aboutToAppear() { // 1. LoadingDialog组件加载完毕侦听数据
  getContext(this).eventHub.on('ccav1', (data:string) =>{
    console.log('slj ', data)
    this.message = `加载中${data}%`
  })
}
​
build() {
  Column({ space: 10 }) {
    LoadingProgress().width(48).height(48).color(Color.White)
    if (this.message) {
      Text(this.message).fontSize(14).fontColor('#fff')
    }
  }
  .justifyContent(FlexAlign.Center)
  .width(120)
  .height(120)
  .backgroundColor('rgba(0,0,0,0.4)')
  .borderRadius(16)
}
}
​
@Entry
@Component
struct Index {
​
// 首次初始化给LoadingDialog传递了message数据 我希望每个3s传递一次如何实现// 方案1:Index.ets AppStorage.setOrCreate每个3s寸一下数据   然后@CustomDialog每个3s获取一下数据 AppStorage.getStorage
// 方法2:同Ability通信 @CustomDialog侦听推送的数据,     Index.ets推送数据// eventHub
// 侦听 getContext(this).eventHub.on('自定义监听频道', (data:string,数据n:类型...) => console.log(data) )
// 推送 getContext(this).eventHub.emit('自定义监听频道', 数据, 数据n)// emitter
// 侦听 emitter.on( {eventId:通信唯一标识}, (e:emitter.EventData) => console.log(data) )
// 推送 emitter.emit( {eventId:通信唯一标识}, { data:{content:数据} } )
​
dialog: CustomDialogController = new CustomDialogController({
  builder: LoadingDialog({ message: '加载中...' }),
  customStyle: true,
  alignment: DialogAlignment.Center
})
​
@State list: number[] = []
aboutToAppear() {
​
  let temp = 0
  setInterval(() => {
    temp++
    getContext(this).eventHub.emit('ccav1', temp*10)
  }, 1000)
​
​
  this.dialog.open()
  // 8s后拿到api数据
  setTimeout(() => {
    this.list = [1,2,3,4]
    this.dialog.close()
  }, 5000)
}
​
build() {
  Column() {
    // 接口数据
    ForEach(this.list, () => {
      Text('接口数据')
    })
  }.width('100%').padding(30)
}
}

c)Emitter

developer.huawei.com/consumer/cn…

developer.huawei.com/consumer/cn…

  • loading
import { emitter } from "@kit.BasicServicesKit"@CustomDialog
export struct LoadingDialog {
controller?: CustomDialogController@Prop message: string = ''
​
​
aboutToAppear() { // 1. LoadingDialog组件加载完毕侦听数据
  // getContext(this).eventHub.on('ccav1', (data:string) =>{
  //   console.log('slj ', data)
  //   this.message = `加载中${data}%`
  // })
​
  emitter.on({eventId: 2404}, (e:emitter.EventData) => {
    this.message = `加载中${e.data?.content}%`
  })
}
​
build() {
  Column({ space: 10 }) {
    LoadingProgress().width(48).height(48).color(Color.White)
    if (this.message) {
      Text(this.message).fontSize(14).fontColor('#fff')
    }
  }
  .justifyContent(FlexAlign.Center)
  .width(120)
  .height(120)
  .backgroundColor('rgba(0,0,0,0.4)')
  .borderRadius(16)
}
}
​
@Entry
@Component
struct Index {
​
// 首次初始化给LoadingDialog传递了message数据 我希望每个3s传递一次如何实现// 方案1:Index.ets AppStorage.setOrCreate每个3s寸一下数据   然后@CustomDialog每个3s获取一下数据 AppStorage.getStorage
// 方法2:同Ability通信 @CustomDialog侦听推送的数据,     Index.ets推送数据// eventHub
// 侦听 getContext(this).eventHub.on('自定义监听频道', (data:string,数据n:类型...) => console.log(data) )
// 推送 getContext(this).eventHub.emit('自定义监听频道', 数据, 数据n)// emitter
// 侦听 emitter.on( {eventId:通信唯一标识}, (e:emitter.EventData) => console.log(data) )
// 推送 emitter.emit( {eventId:通信唯一标识}, { data:{content:数据} } )dialog: CustomDialogController = new CustomDialogController({
  builder: LoadingDialog({ message: '加载中...' }),
  customStyle: true,
  alignment: DialogAlignment.Center
})
​
@State list: number[] = []
aboutToAppear() {
​
  let temp = 0
  setInterval(() => {
    temp++
    // getContext(this).eventHub.emit('ccav1', temp*10)
    emitter.emit({eventId:2404}, {
      data: {
        content: temp*10,
      }
    })
  }, 1000)
​
​
  this.dialog.open()
  // 8s后拿到api数据
  setTimeout(() => {
    this.list = [1,2,3,4]
    this.dialog.close()
  }, 5000)
}
​
build() {
  Column() {
    // 接口数据
    ForEach(this.list, () => {
      Text('接口数据')
    })
  }.width('100%').padding(30)
}
}
  • @Concurrent
import { emitter } from '@kit.BasicServicesKit'
import { taskpool } from '@kit.ArkTS'
import { common } from '@kit.AbilityKit'// eventHub
// 侦听 getContext(this).eventHub.on('自定义监听频道', (data:string,数据n:类型...) => console.log(data) )
// 推送 getContext(this).eventHub.emit('自定义监听频道', 数据, 数据n)// emitter
// 侦听 emitter.on( {eventId:通信唯一标识}, (e:emitter.EventData) => console.log(data) )
// 推送 emitter.emit( {eventId:通信唯一标识}, { data:{content:数据} } )@Concurrent
function testThread(context:common.UIAbilityContext) {
console.log('testThread')
​
context.eventHub.on('ccav', (data:string) => console.log('slj ', data) ) // ❌// emitter.on({eventId:2404}, (e: emitter.EventData) => {
//   console.log('slj data ', e.data?.content)
// })
}
​
@Entry
@Component
struct Test {
aboutToAppear() {
  taskpool.execute(testThread, getContext(this))
}
build() {
  Button('线程间通信').onClick(() => {
    getContext(this).eventHub.emit('ccav', '老6 行不行')
​
    // emitter.emit({eventId:2404}, {
    //   data: {
    //     content: 'hello 交个朋友👬🏻'
    //   }
    // })
  })
}
}

d)CommonEvent自定义公共事件

// import { emitter } from '@kit.BasicServicesKit'
import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
import { taskpool } from '@kit.ArkTS'
import { common } from '@kit.AbilityKit'// eventHub
// 侦听 getContext(this).eventHub.on('自定义监听频道', (data:string,数据n:类型...) => console.log(data) )
// 推送 getContext(this).eventHub.emit('自定义监听频道', 数据, 数据n)// emitter
// 侦听 emitter.on( {eventId:通信唯一标识}, (e:emitter.EventData) => console.log(data) )
// 推送 emitter.emit( {eventId:通信唯一标识}, { data:{content:数据} } )// 公共事件
// 侦听 createSubscriber、subscribe
// 推送 publish@Concurrent
function testThread() {
// =======
let subscriber: commonEventManager.CommonEventSubscriber | null = null;
let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
  events: ['hm2404'], // 公共事件 锁屏等等、自定义事件
};
commonEventManager.createSubscriber(subscribeInfo, (err: BusinessError, data: commonEventManager.CommonEventSubscriber) => {
  if (err) {
    console.log(`slj Failed to create subscriber. Code is ${err.code}, message is ${err.message}`);
    return;
  }
  console.log( 'slj Succeeded in creating subscriber.');
  subscriber = data;
  commonEventManager.subscribe(subscriber, (err: BusinessError, data: commonEventManager.CommonEventData) => {
    if (err) {
      console.log(`slj Failed to subscribe common event. Code is ${err.code}, message is ${err.message}`);
      return;
    }
    console.log('slj 推送的数据:' , JSON.stringify(data))
    // ...
  })
})
​
// =======
}
​
@Entry
@Component
struct Test {
aboutToAppear() {
  taskpool.execute(testThread)
}
build() {
  Button('线程间通信').onClick(() => {
    commonEventManager.publish('hm2404', {
      code: 1, // 公共事件的初始代码
      data: "initial data hello 我给你推送数据了", // 公共事件的初始数据
    }, (err: BusinessError) => {
      if (err) {
        console.log(`slj Publish failed, code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err.message)}`);
      } else {
        //...
        console.log(`slj Publish success`);
      }
    });
  })
}
}

e)CommonEvent系统公共事件

  • 1
{
    "name": "reason_common_event_sticky",
    "value": "允许应用在公共事件监听场景中发布粘性公共事件"
  }
   
   
  "requestPermissions": [
    {
      "name": "ohos.permission.COMMONEVENT_STICKY",
      "reason": "$string:reason_common_event_sticky",
      "usedScene": {
        "abilities": [
          "EntryAbility",
          "LauncherAbility"
        ],
        "when": "always"
      }
    }
  ],
  • 2
import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
import { taskpool } from '@kit.ArkTS'@Entry
@Component
struct Test {
aboutToAppear() {
  let subscriber: commonEventManager.CommonEventSubscriber | null = null;
  let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
    events: [ // ✅
      commonEventManager.Support.COMMON_EVENT_WIFI_POWER_STATE,
      commonEventManager.Support.COMMON_EVENT_SCREEN_OFF,
      commonEventManager.Support.COMMON_EVENT_SCREEN_ON,
      commonEventManager.Support.COMMON_EVENT_SCREEN_LOCKED,
      commonEventManager.Support.COMMON_EVENT_SCREEN_UNLOCKED,
    ]
  };
  commonEventManager.createSubscriber(subscribeInfo, (err: BusinessError, data: commonEventManager.CommonEventSubscriber) => {
    if (err) {
      console.log(`slj Failed to create subscriber. Code is ${err.code}, message is ${err.message}`);
      return;
    }
    console.log( 'slj Succeeded in creating subscriber.');
    subscriber = data;
    commonEventManager.subscribe(subscriber, (err: BusinessError, data: commonEventManager.CommonEventData) => {
      if (err) {
        console.log(`slj Failed to subscribe common event. Code is ${err.code}, message is ${err.message}`);
        return;
      }
      console.log('slj 用户有动作啦 干他 ✅ :' , JSON.stringify(data))
      console.log('slj 用户有动作啦 干他 ✅ :' , JSON.stringify(data))
      console.log('slj 用户有动作啦 干他 ✅ :' , JSON.stringify(data))
      console.log('slj 用户有动作啦 干他 ✅ :' , JSON.stringify(data))
      // ...
    })
  })
​
}
build() {
}
}

f)Worker/Taskpool

  • 在taskpool中,@Concurrent任务池子线程返回数据,execute执行任务获取数据 从而实现线程通信
  • 在taskpool中,@Concurrent任务池子线程通过taskpool.Task.sendData发送数据,宿主线程监听到的数据task.onReceiveData((result: string) => {})
  • 在worker中,彼此通过postMessage发送、通过onmessage获取消息
  • 在worker中,Worker同步调用宿主线程的接口
宿主线程 workerInstance.registerGlobalCallObject("picData", picData);
然后,在Worker中通过callGlobalCallObjectMethod接口就可以调用宿主线程中的setUp()方法了。

g)IPC&RPC

服务卡片 和 app之间的通信

developer.huawei.com/consumer/cn…

10. 知识点小总结

计算机:CPU、内存、硬盘、显卡等等

同步异步:同步-按书写顺序挨个执行的、异步-耗时的代码例如setTimeout/setInterval/IO操作

串行/并发/并行:串行-同步的表现、并发-多个任务一段时间内快速切换执行(咖啡店有两个队伍 一个门店快速切换执行)、并行-多个任务真正同时执行 (有两个门店)

CPU/IO密集计算:CPU密集-耗脑瓜子的数学计算/图像处理/加密解密/压缩/解压/JSON解析、IO密集-网络下载/数据库操作/压缩/解压缩

进程线程:进程-正在执行的程序的实例、 线程-是进程内部的执行单位

异步并发:promise、async/await

什么是promise:异步编程解决方案  es6出来的
promise三个状态:进行中、成功了、失败了
哪里用pormise:http模块、很多api .then处理
async/await语法糖用来装饰promise 视觉上有一个同步的效果

多线程并发taskpool: 函数加一个装饰器@Concurrent

多线程并发Worker :右击创建Worker、private workerThread = worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/Worker1.ets');

this.workerThread.postMessage()
this.workerThread.onmessage = () => {}

线程通信

// eventHub   线程内通信
// 侦听 getContext(this).eventHub.on('自定义监听频道', (data:string,数据n:类型...) => console.log(data) )
// 推送 getContext(this).eventHub.emit('自定义监听频道', 数据, 数据n)

// emitter    线程间通信
// 侦听 emitter.on(  {eventId:通信唯一标识}, (e:emitter.EventData) => console.log(data) )
// 推送 emitter.emit( {eventId:通信唯一标识}, {  data:{content:数据} } )

// 公共事件-自定义事件
// 侦听  createSubscriber、subscribe
// 推送  publish


- 在taskpool中,@Concurrent任务池子线程返回数据,execute执行任务获取数据  从而实现线程通信 
- 在taskpool中,@Concurrent任务池子线程通过taskpool.Task.sendData发送数据,宿主线程监听到的数据task.onReceiveData((result: string) => {})
- 在worker中,彼此通过postMessage发送、通过onmessage获取消息
- 在worker中,Worker同步调用宿主线程的接口
```
宿主线程  workerInstance.registerGlobalCallObject("picData", picData);
然后,在Worker中通过callGlobalCallObjectMethod接口就可以调用宿主线程中的setUp()方法了。
```

11. 企业级面试题

 - 谈谈你对进程线程的理解 💕 💕

进程:

  • 进程是正在执行的程序的实例,它是系统资源分配的基本单位。
  • 进程是一个独立的程序运行环境
  • 是系统进行资源分配的基本单位

线程:

  • 线程是进程内部的执行单位
  • 是操作系统能够进行运算调度的最小单位。

- 鸿蒙线程之间内存共享吗?

developer.huawei.com/consumer/cn…

不共享

ArkTS采用消息通信的Actor并发模型 所以具有内存隔离特性

java是内存共享的并发模型

- 鸿蒙arkts是单线程还是多线程、为什么是单线程呢

- 鸿蒙 stage 是单线程还是多线程

ArkTS是单线程的,单线程可以避免锁机制

- 鸿蒙如何实现多线程  💕 💕

taskpool、worker

- 进程/线程通信 💕 💕

通信场景能力支持
同Ability通信Emitter、EventHub、CommonEvent
跨Ability通信Emitter、EventHub、CommonEvent
跨线程通信Emitter、CommonEvent、Worker、Taskpool
跨进程通信CommonEvent、IPC&RPC

dataShare跨应用通信:用于应用管理其自身数据,同时支持同个设备上不同应用间的数据共享。

// eventHub   线程内通信
// 侦听 getContext(this).eventHub.on('自定义监听频道', (data:string,数据n:类型...) => console.log(data) )
// 推送 getContext(this).eventHub.emit('自定义监听频道', 数据, 数据n)

// emitter    线程间通信
// 侦听 emitter.on(  {eventId:通信唯一标识}, (e:emitter.EventData) => console.log(data) )
// 推送 emitter.emit( {eventId:通信唯一标识}, {  data:{content:数据} } )

// 公共事件-自定义事件
// 侦听  createSubscriber、subscribe
// 推送  publish


- 在taskpool中,@Concurrent任务池子线程返回数据,execute执行任务获取数据  从而实现线程通信 
- 在taskpool中,@Concurrent任务池子线程通过taskpool.Task.sendData发送数据,宿主线程监听到的数据task.onReceiveData((result: string) => {})
- 在worker中,彼此通过postMessage发送、通过onmessage获取消息
- 在worker中,Worker同步调用宿主线程的接口
```
宿主线程  workerInstance.registerGlobalCallObject("picData", picData);
然后,在Worker中通过callGlobalCallObjectMethod接口就可以调用宿主线程中的setUp()方法了。
```

- taskpool和worker区别 💕 💕

短时任务用TaskPool、长时任务用worker

场景对比

TaskPool和Worker均支持多线程并发能力。由于TaskPool的工作线程会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容),而Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级,故在性能方面使用TaskPool会优于Worker,因此大多数场景推荐使用TaskPool。

TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟且非长时任务)会被系统自动回收;而Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。

常见的一些开发场景及适用具体说明如下:

  • 运行时间超过3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时)的任务。例如后台进行1小时的预测算法训练等CPU密集型任务,需要使用Worker。场景示例可参考常驻任务开发指导
  • 有关联的一系列同步任务。例如在一些需要创建、使用句柄的场景中,句柄创建每次都是不同的,该句柄需永久保存,保证使用该句柄进行操作,需要使用Worker。场景示例可参考使用Worker处理关联的同步任务
  • 需要设置优先级的任务。例如图库直方图绘制场景,后台计算的直方图数据会用于前台界面的显示,影响用户体验,需要高优先级处理,需要使用TaskPool。
  • 需要频繁取消的任务。例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool。
  • 大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用Worker去做负载管理,推荐采用TaskPool。场景示例可参考批量数据写数据库场景

TaskPool和Worker的实现特点对比

实现TaskPoolWorker
内存模型线程间隔离,内存不共享。线程间隔离,内存不共享。
参数传递机制采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。
参数传递直接传递,无需封装,默认进行transfer。消息对象唯一参数,需要自己封装。
方法调用直接将方法传入调用。在Worker线程中进行消息解析并调用对应方法。
返回值异步调用后默认返回。主动发送消息,需在onmessage解析赋值。
生命周期TaskPool自行管理生命周期,无需关心任务负载高低。开发者自行管理Worker的数量及生命周期。
任务池个数上限自动管理,无需配置。同个进程下,最多支持同时开启64个Worker线程,实际数量由进程内存决定。
任务执行时长上限3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时),长时任务无执行时长上限。无限制。
设置任务的优先级支持配置任务优先级。不支持。
执行任务的取消支持取消已经发起的任务。不支持。
线程复用支持。不支持。
任务延时执行支持。不支持。
设置任务依赖关系支持。不支持。
串行队列支持。不支持。
任务组支持。不支持。

- Worker和TaskPool的线程数量是否有限制

TaskPool内部会动态调整线程个数,不支持设置数量,只需要往线程池中抛任务,确保高优先级任务的及时执行。

Worker的线程个数最多64个,如果Worker超过规定个数,会创建失败。

在使用时,TaskPool与Worker两者独立,不相互影响,因此Worker在达到上限数量时,不会影响TaskPool。Worker是固定数量,当前是64个。TaskPool线程池的数量会根据硬件条件、任务负载等情况动态调整。

- 鸿蒙有几个进程线程 

系统的进程模型如下图所示。

  • 通常情况下,应用中(同一Bundle名称)的所有UIAbility、ServiceExtensionAbility和DataShareExtensionAbility均是运行在同一个独立进程(主进程)中,如下图中绿色部分的“Main Process”。

UIAbility 页面能力 窗口 -> 加载页面

ServiceExtensionAbility 后台服务扩展能力,提供后台运行并对外提供相应能力。

DataShareExtensionAbility 数据共享扩展能力,用于对外提供数据读写服务。

  • 应用中(同一Bundle名称)的所有同一类型ExtensionAbility(除ServiceExtensionAbility和DataShareExtensionAbility外)均是运行在一个独立进程中,如下图中蓝色部分的“FormExtensionAbility Process”、“InputMethodExtensionAbility Process”、其他ExtensionAbility Process。

FormExtensionAbility:卡片扩展能力,提供卡片开发能力。

WorkSchedulerExtensionAbility:延时任务扩展能力,允许应用在系统闲时执行实时性不高的任务。

InputMethodExtensionAbility:输入法扩展能力,用于开发输入法应用。

AccessibilityExtensionAbility:无障碍服务扩展能力,支持访问与操作前台界面。

FileShareExtensionAbility:文件共享扩展能力,用于应用间的文件分享。预留能力,仅系统应用支持。

等等

  • WebView拥有独立的渲染进程,如下图中黄色部分的“Render Process”。

鸿蒙加载h5网页

图1 进程模型示意图

编辑

  • Stage模型下的线程主要有如下三类:
    • 主线程
      • 执行UI绘制。
      • 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
      • 管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程。
      • 分发交互事件。
      • 处理应用代码的回调,包括事件处理和生命周期管理。
      • 接收TaskPool以及Worker线程发送的消息。
    • TaskPool Worker线程
      • 用于执行耗时操作,支持设置调度优先级、负载均衡等功能,推荐使用。
    • Worker线程
      • 用于执行耗时操作,支持线程间通信。

TaskPool与Worker的运作机制、通信手段和使用方法可以参考TaskPool和Worker的对比

编辑

- 主线程有哪些职责呢 

  • 主线程
    • 执行UI绘制。
    • 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
    • 管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程。
    • 分发交互事件。
    • 处理应用代码的回调,包括事件处理和生命周期管理。
    • 接收TaskPool以及Worker线程发送的消息。

- 如何实现异步开发/并发/处理 

Promise和async/await

- 谈谈你对pormise的理解,有几个状态 

异步编程解决方案

romise有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。Promise对象创建后处于pending状态,并在异步操作完成后转换为fulfilled或rejected状态。

- promise和async、await关系 

promise是异步编程解决方案

async/await是一种用于处理异步操作的Promise语法糖,使得编写异步代码变得更加简单和易读。通过使用async关键字声明一个函数为异步函数,并使用await关键字等待Promise的解析(完成或拒绝),以同步的方式编写异步操作的代码。 

一般做项目接口返回的数据都保存在promise中,咱们通过async/await就可以获取出来

- 异步处理和主线程有什么关系、如何通信

异步操作的设计初衷就是为了减少对主线程(UI线程)的影响,从而提升应用的响应性和用户体验。

通信直接修改@State状态就行

- 你的项目中有没有用到多线程

developer.huawei.com/consumer/cn…

- promise 并发是单线程还是多线程

遇到pormise会放到微任务队列,然后等js线程执行完毕,再由event loop事件循环对象把promise数据交给js线程处理

- 子线程和主线程的优先级及任务执行策略是什么

主线程作为UI线程,拥有最高优先级。在负载较高时,执行会更快;负载较低时,效率差别不大。

子线程可以通过优先级设置,任务优先级等影响调度。

- 在多线程并发场景中,如何实现安全访问同一块共享内存

可以使用共享对象SharedArrayBuffer实现。SharedArrayBuffer对象存储的数据在同时被修改时,需要通过Atomics原子操作保证其同步性,即下个操作开始之前务必需要等到上个操作已经结束。

鸿蒙开发者班级

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

    ^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤