鸿蒙-多线程并发

206 阅读9分钟

多线程概述

当前ArkTS提供了TaskPool和Worker两种并发能力,TaskPool和Worker都基于Actor并发模型实现。

Actor并发模型作为基于消息通信并发模型的典型代表,不需要开发者去面对线程锁带来的一系列复杂偶发的问题,同时并发度也相对较高,因此得到了广泛的支持和使用。

TaskPool

需要设置优先级的任务,需要使用TaskPool
需要频繁取消的任务。需要使用TaskPool
大量或者调度点较分散的任务。不方便使用Worker去做负载管理,推荐采用TaskPool

工作原理

  • TaskPool在Worker之上实现了调度器和Worker线程池
  • TaskPool的工作线程会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容)
  • TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟且非长时任务)会被系统自动回收

image.png

使用流程

  1. 导入包
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)能力,可以将多个任务加入到串行队列中,使多个任务按照一定的顺序依次执行,而不会出现并发或乱序情况

常见的业务场景

  1. API执行队列:顺序调用模块接口
  2. 渲染指令队列:时序性的操作DOM和渲染
  3. 启动时遍历程序包:遍历程序包、清理包、资源加载等串行操作

具体实现

  1. 模拟耗时操作
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(移除任务依赖)两个接口

常见业务场景

  1. 图片解码:将图片拆成n份放到n个任务中执行,都依赖同一个任务对结果进行处理
  2. 数据库操作:A任务获取执行需要B任务执行结果
  3. 网络下载: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任务组,通过创建任务组完成多任务同步等待结果

常见业务场景

  1. 图片解析成直方图:一张图片并发加速,拆成多个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(高:中:低)

常见业务场景

  1. 处理耗时的图片数据:拍摄输入或美化图片放在TaskPool中处理,需要毫秒内返回主线程渲染,为保证用户体验和即时性,可以设置为高优先级
  2. 日志落盘:日志写到文件或数据库,可以设置为低优先级

具体实现

@Concurrent
function print(str:string){
    console.log(str)
}
let task=new taskpool.Task(print,'test')
taskpool.execute(task,taskpool.Priority.LOW)

任务延时调度

有些任务需要延时一段时间后才需要执行。Taskpool提供延时调度机制,通过executeDelayed实现

常见业务场景

  1. 缓存业务延时执行:应用启动时,存在大量低优先级任务。可以延时执行,防止影响冷启动耗时

具体实现

@Concurrent
function downloadFile(){
    console.log('success')
}
let task=new taskpool.Task(downloadFile)
taskpool.executeDelayed(3000,task,taskpool.Priority.HIGH)

Worker

概述

  1. 同时运行的Worker子线程数量上限为64个
  2. Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级,故在性能方面使用TaskPool会优于Worker
  3. Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期
  4. 运行时间超过3分钟,不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时的任务。需要使用Worker
  5. 有关联的一系列同步任务。需要使用Worker。

worker宿主线程的监听事件的回调

onmessage:接收worker线程通过postMessage接口发送的消息时被调用的事件处理程序
onerror:worker在执行过程中发生异常被调用的事件处理程序
onmessageerror:worker对象接收到一条无法被序列化的消息时被调用的事件处理程序
onexit:worker销毁时被调用的事件处理程序

使用流程

  1. 创建worker

手动创建

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

IDE创建
新建 => Worker

  1. 创建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

概述

  1. Sendable class只能继承Sendable class,普通class不能继承
  2. 禁止使用闭包变量,不支持计算属性
  3. 成员属性显式初始化
  4. 不支持增加和删除属性
  5. 适用于TaskPool或Worker中使用类方法和传输对象数据量较大的使用场景

image.png

// 定义了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_++
        })
    }
}