多线程概述
当前ArkTS提供了TaskPool和Worker两种并发能力,TaskPool和Worker都基于Actor并发模型实现。
Actor并发模型作为基于消息通信并发模型的典型代表,不需要开发者去面对线程锁带来的一系列复杂偶发的问题,同时并发度也相对较高,因此得到了广泛的支持和使用。
TaskPool
需要设置优先级的任务,需要使用TaskPool
需要频繁取消的任务。需要使用TaskPool
大量或者调度点较分散的任务。不方便使用Worker去做负载管理,推荐采用TaskPool
工作原理
- TaskPool在Worker之上实现了调度器和Worker线程池
- TaskPool的工作线程会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容)
- TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟且非长时任务)会被系统自动回收
使用流程
- 导入包
import {taskpool} from '@kit.ArkTS'
2. 创建并发函数
@Concurrent
function print(str:string){
console.log(str)
}
3. 创建task任务
let task=new taskpool.Task(print,'test')
4. 执行任务,获取结果
taskpool.execute(task).then(()=>{
console.log('execute success')
})
多任务关联执行(串行顺序依赖)
任务需要串行执行。ArkTS提供串行队列(SequenceRunner)能力,可以将多个任务加入到串行队列中,使多个任务按照一定的顺序依次执行,而不会出现并发或乱序情况
常见的业务场景
- API执行队列:顺序调用模块接口
- 渲染指令队列:时序性的操作DOM和渲染
- 启动时遍历程序包:遍历程序包、清理包、资源加载等串行操作
具体实现
- 模拟耗时操作
import {taskpool} from '@kit.ArkTS'
@Concurrent
function additionDelay(delay:number):void{
let start:number=new Date().getTime()
while(new Date().getTime-start<delay){
continue
}
}
@Concurrent
function waitForRunner(resString:string):string{
ruturn resString
}
2. SequenceRunner的实现
async function seqRunner(){
let result:string=''
let task1:taskpool.Task=new taskpool.Task(additionDelay,300)
let task2:taskpool.Task=new taskpool.Task(additionDelay,200)
let task3:taskpool.Task=new taskpool.Task(additionDelay,100)
let task4:taskpool.Task=new taskpool.Task(waitForRunner,50)
let runner:taskpool.SequenceRunner=new taskpool.SequenceRunner()
runner.execute(task1).then(()=>{
result+='a'
})
runner.execute(task2).then(()=>{
result+='b'
})
runner.execute(task3).then(()=>{
result+='c'
})
await runner.execute(task4)
console.log('reulst:'+result)
}
多任务关联执行(树状依赖)
一个任务在另一个任务完成后才执行。TaskPool提供addDependency(添加任务依赖)和removeDependency(移除任务依赖)两个接口
常见业务场景
- 图片解码:将图片拆成n份放到n个任务中执行,都依赖同一个任务对结果进行处理
- 数据库操作:A任务获取执行需要B任务执行结果
- 网络下载:B任务数据处理依赖A任务下载数据的结果
具体实现
function AddDependencyTest(){
let task1:taskpool.Task=new taskpool.Task(additionDelay,100)
let task2:taskpool.Task=new taskpool.Task(additionDelay,200)
let task3:taskpool.Task=new taskpool.Task(additionDelay,300)
task1.addDependency(task2)
task2.addDependency(task3)
taskpool.execute(task1).then((res:object)=>{
console.info('task1 res:'+res)
})
taskpool.execute(task2).then((res:object)=>{
console.info('task2 res:'+res)
})
taskpool.execute(task3).then((res:object)=>{
console.info('task3 res:'+res)
})
}
多任务同步等待结果
多任务并发执行,等所有任务执行完毕后统一返回结果。TaskPool提供TaskGroup任务组,通过创建任务组完成多任务同步等待结果
常见业务场景
- 图片解析成直方图:一张图片并发加速,拆成多个ArrayBuffer进行解析,将所有任务解析完成后拼成完整直方图渲染
具体实现
let taskGroup:taskpool.TaskGroup=new taskpool.TaskGroup()
let Task_pool_capacity:number=10
function histogramStatistic(pixelBuffer:ArrayBuffer):void{
let byteLengthOfTask:number=pixelBuffer.byteLength
for(let i=0;i<Task_pool_capacity;i++){
let dataSlice:Object=(i===Task_pool_capacity-1)?pixelBuffer.slice(i*byteLengthOfTask):pixelBuffer.slice(i*byteLengthOfTask,(i+1)*byteLengthOfTask)
let task:taskpool.Task=new taskpool.Task(imageProcessing,dataSlice)
taskpool.execute(taskGroup,taskpool.priority.HIGH)
.then((res:Object[]):void|Promise<void>=>{})
.catch((error:Error)=>{
console.error(error)
})
}
}
多任务优先级调度
在资源既定的情况下,系统会分配更多资源优先处理高优先级任务,尽量保证此类任务的即时性。TaskPool提供了HIGH、MEDIUM、LOW以及IDLE(高、中、低、后台)四种优先级任务,调度为M:N:1(高:中:低)
常见业务场景
- 处理耗时的图片数据:拍摄输入或美化图片放在TaskPool中处理,需要毫秒内返回主线程渲染,为保证用户体验和即时性,可以设置为高优先级
- 日志落盘:日志写到文件或数据库,可以设置为低优先级
具体实现
@Concurrent
function print(str:string){
console.log(str)
}
let task=new taskpool.Task(print,'test')
taskpool.execute(task,taskpool.Priority.LOW)
任务延时调度
有些任务需要延时一段时间后才需要执行。Taskpool提供延时调度机制,通过executeDelayed实现
常见业务场景
- 缓存业务延时执行:应用启动时,存在大量低优先级任务。可以延时执行,防止影响冷启动耗时
具体实现
@Concurrent
function downloadFile(){
console.log('success')
}
let task=new taskpool.Task(downloadFile)
taskpool.executeDelayed(3000,task,taskpool.Priority.HIGH)
Worker
概述
- 同时运行的Worker子线程数量上限为64个
- Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级,故在性能方面使用TaskPool会优于Worker
- Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期
- 运行时间超过3分钟,不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时的任务。需要使用Worker
- 有关联的一系列同步任务。需要使用Worker。
worker宿主线程的监听事件的回调
onmessage:接收worker线程通过postMessage接口发送的消息时被调用的事件处理程序
onerror:worker在执行过程中发生异常被调用的事件处理程序
onmessageerror:worker对象接收到一条无法被序列化的消息时被调用的事件处理程序
onexit:worker销毁时被调用的事件处理程序
使用流程
- 创建worker
手动创建
"buildOption":{
"sourceOption":{
"workers":[
"./src/main/ets/workers/worker.ets"
]
}
}
IDE创建
新建 => Worker
- 创建worker对象
import {worker} from '@kit.ArkTS'
// HAP {moduleName}/ets/{relativePath}
const worker1:worker.ThreadWorker=new worker.ThreadWorker("entry/ets/workers/worker.ets")
// HSP {moduleName}/ets/{relativePath}
const worker2:worker.ThreadWorker=new worker.TreadWorker("hsp/ets/workers/worker.ets")
// HAR @{moduleName}/ets/{relativePath} \ 相对路径
const worker3:worker.ThreadWorker=new worker.ThreadWorker("@har/ets/workers/worker.ets")
const worker4:worker.ThreadWorker=new wokrer.ThreadWorker("./workers/worker.ets")
3. 执行任务
// index.ets
import {worker} from '@kit.ArkTS'
const workerInstance=new worker.ThreadWorker("entry/ets/workers/worker.ets)
workerInstance.postMessage("hello world") // 向worker线程发送消息
// entry/ets/workers.worker.ets
import {worker,MessageEvents} from '@kit.ArkTS'
const workerPort=worker.workerPort
workerPort.onmessage=(e:MessageEvents):void=>{ // worker线程接收消息
console.log("main message")
}
4. 线程销毁
宿主线程销毁
// index.ets
const worker1:worker.ThreadWorker=new worker.ThreadWorker("entry/ets/workers/worker.ets")
worker1.terminate()
worker线程销毁
// entry/ets/workers/worker.ets
const workerPort=worker.workerPort
workerPort.onmessage=(e:MessageEvents):void=>{
workerPort.close()
}
线程通信
交互场景 | 通信方式 |
---|---|
宿主js线程->TaskPool线程 | 参数传递后分发任务;过程中不支持正向通信 |
TaskPool线程->宿主js线程 | 结果返回;sendData触发宿主线程异步回调 |
宿主js线程->worker线程 | 采用postMessage&onmessage异步通信 |
worker线程->宿主js线程 | 采用postMessage&onmessage异步通信 |
任意线程 <->任意线程 | 使用@ohos.emitter实现双向异步通信 |
1. 宿主->TaskPool线程
参数传递后分发任务
@Concurrent
function print(str:string){
console.log(str)
}
let task=new taskpoolTask(print,'test')
let res=taskpool.excute(task)
2. TaskPool线程->宿主线程
@Concurrent
function sendDataTest(num:number):number{
let res:number=num*10
taskpool.Task.sendData(res)
return num
}
function printLog(data:number):void{
console.log('taskpool:data is'+data)
}
let task:taskpool.Task=new taskpool.Task(sendDataTest,1)
task.onReceiveData(printLog)
let res=awiat taskpool.execute(task)
console.log('taskpool execute res is',res)
3. 宿主线程->worker线程
// index.ets
const workerInstance=new worker.ThreadWorker('entry/ets/workers/Worker.ets')
let str='test worker'
workerInstance.postMessage(str) 发送消息
// entry/ets/workders/Worker.ets
const workerPort:ThreadWorkerGlobalScope=worker.workerPort
workerPort.onmessage=(e:MessageEvents)=>{ // 接收消息
let str:string=e.data
console.log('workerprint',str)
}
4. worker线程->宿主线程
采用postMessage&onmessage异步通信
// index.ets
const workerInstance=new worker.ThreadWorker('entry/ets/workers/Worker.ets')
let str='test worker'
workerInstance.postMessage(str)
workerInstance.onmessage=(e:MessageEvents):void=>{ // 接收消息
console.log(e.data)
}
// entry/ets/workers/Worker.ets
workerPort.onmessage=(e:MessageEvents)=>{ // 发送消息
let str:string=e.data
console.log('worker print',str)
workerPort.postMessage('worker return'+str)
}
5. 任意线程间通信
// index.ets
let innerEvent:emitter.InnerEvent={
eventId:1
}
emitter.on(innerEvent,(e)={ // 持续订阅指定的事件
console.info('emitter result is',JSON.stringify(e.data))
})
// entry/ets/workers/Worker.ets
let eventData:emitter.EventData={data:{'data':'worker data'}}
let innerEvent:emitter.InnerEvent={
eventId:1,
priority:emitter.EventPriority.HIGH
}
emmiter.emit(innerEvent,eventData) // 发送指定优先级事件
6. 单例模式
export class Singleton{
private static instance:Singleton=new Singleton()
private constructor(){}
public static getInstance():Singleton{
return Singleton instance
}
}
// index.ets
taskpool.execute(func,Singleton.getInstance())
@Concurrent
function func(instance:Singleton){
// false
console.log('是否相等:'+(Singleton.getInstance()===instance))
}
因为HarmonyOS上面使用的是内存隔离的线程模型(Actor),不同线程之间内存隔离,对象在不同线程之间使用是通过序列化的方式。所以会出现单例在不同线程中创建多份的问题
sendable
概述
- Sendable class只能继承Sendable class,普通class不能继承
- 禁止使用闭包变量,不支持计算属性
- 成员属性显式初始化
- 不支持增加和删除属性
- 适用于TaskPool或Worker中使用类方法和传输对象数据量较大的使用场景
// 定义了ArkTS的可共享对象体系及其规格约束。
// 符合Sendable协议的数据可以在ArkTS并发实例间传递
@Sendable
class SendableTestClass{
desc:string='sendable:this is Sendable TestClass'
num:number=5
printName(){
console.info('sendable:SendableTestClass desc is'+this.desc)
}
get getNum():number{
return this.num
}
}
宿主线程->TaskPool线程
参数传递后分发任务(过程中不支持正向通信)
@Sendable
class Item{
name:string=''
data:collections.Map<string,string>=new collections.Map()
// collections 并发场景下高性能数据传递容器集
}
@Concurrent
function executeTask(item:Item){
console.log(util.getHash(item).toString())
}
let item=new Item()
console.log(util.getHash(item).toString()) // 使用Util.getHash()获取对象的Hash值
await taskpool.execute(executeTask,item)
TaskPool线程->宿主线程
@Concurrent
function executeTask():collections.Set<string>{
let set:collections.Set<string>=new collections.Set()
console.log('taskpool set hash is',util.getHash(set))
taskpool.Task.sendData(set)
return set
}
let task:taskpool.Task=new taskpool.Task(executeTask)
task.onReceiveData((data:collections.Set<string>)=>{
console.log('onReceiveData data hash is',util.getHash(data))
})
taskpool.execute(task).then((res)=>{
console.log('taskpool execute result hash is',util.getHash(res))
})
宿主线程->worker线程
// index.ets
@Sendable
class Item{
name:string=''
data:collections.Map<string,string>=new collections.Map()
}
const workerInstance=new worker.ThreadWorker('entry/ets/workers/Worker.ets')
const item=new Item()
item.name='test sendable'
console.log('main data hash is',util.getHash(item))
// 向worker线程发送消息
// 消息中的Sendable对象通过引用传递
// 消息中的非Sendable对象通过序列化传递
workerInstance.postMessageWithSharedSendable(item)
// entry/ets/workers/Worker.ets
workerPort.onmessage=(e:MessageEvents)=>{ // 接收消息
console.log('worker data hash is',util.getHash(e.data))
}
worker->宿主线程
// index.ets
const workerInstance=new worker.ThreadWorker('entry/ets/workers/Worker.ets')
workerInstance.postMessage('')
workerInstance.onmessage=(e:MessageEvents):void=>{ // 接收消息
console.log('main data hash is',util.getHash(e.data))
}
// entry/ets/workers/Worker.ets
@Sendable
class Item{
name:string=''
data:collections.Map<string,string>=new collections.Map()
}
workerPort.onmessage=(e:MessageEvents)=>{
const item=new Item()
item.name='test sendable'
console.log('worker data hash is',util.getHash(item))
workerPort.postMessageWithSharedSendable(item) // 发送消息
}
任意线程
// index.ets
let innerEvent:emitter.InnerEvent={eventId:1}
emitter.on(innerEvent,(e)=>{
console.log('emitter getData hash is',util.getHash(e.data))
})
// entry/ets/workers/Worker.ets
@Sendable
class Item{
name:string=''
data:collections.Map<string,string>=new collections.Map()
}
workerPort.onmessage=(e:MessageEvents)=>{
const item=new Item()
let eventData:emitter.EventData={data:item}
let innerEvent:emitter.InnerEvent={eventId:1,priority:emitter.EventPriority.HIGH}
console.log('worker data hash is',util.getHash(item))
emitter.emit(innerEvent,eventData)
}
单例模式
"use shared" // 标记为共享模块,共享模块是进程只会加载一次的模块
@Sendable
export class Singleton{
private static instance:Singleton=new Singleton()
private constructor(){
}
public static getInstance():Singleton{
return Singleton.Instance
}
}
// index.ets
taskpool.execute(func,Singleton.getInstance())
@Concurrent
function func(instance:Singleton){
console.log('是否相等:'+(Singleton.getInstance()===instance))
}
异步锁
import {ArkTSUtils} from '@kit.ArkTS'
'use shared'
@Sendable
export class SingletonA{
private count:number=0
private static instance:SingletonA=new SingletonA()
static getInstance():SingletonA{
return SingletonA.instance
}
}
import {ArkTSUtils} from '@kit.ArkTS'
'use shared'
@Sendable
export class SingletonA{
private count_:number=0
lock_:ArkTSUtils.locks.AsyncLock=new ArkTSUtils.AsyncLock()
pubilc async getCount():Promise<number>{
return this.lock_.lockAsync(()=>{
return this.count_
})
}
public async increaseCount(){
await this.lock._lockAsync(()=>{
this.count_++
})
}
}