鸿蒙多线程并发
鸿蒙开发提供两种并发能力:TaskPool和Worker
两种能力都是基于Actor模型实现,是典型的内存不共享并发模型,每个线程都会创建独立的Actor,管理各自的独立内存,彼此之间通过通信机制实现数据交流
以经典的消费生产问题为例
内存共享模型下通过信号量PV操作模拟:
- 生产者生成数据后,放在一个缓冲区
- 消费者从缓冲区取出数据
- 任何时刻只允许一个消费者或生产者访问缓冲区
说明
- 操作缓冲区需要互斥
- 缓冲区空时,消费者需要等待生产者生产,需要同步
需要三个信号量
- 互斥信号量murex:用于访问缓冲区,初始化为1;
- 资源信号量fullBuffers:用于消费者询问缓冲区是否有数据,有数据则读取,初始化为0(缓冲区一开始为空)
- 资源信号量emptyBuffers:用于生产者询问缓冲区是否有空位,有空位则生成数据,初始化为n(缓冲区大小)
void Producer(){
while(TRUE){
P(emptyBuffers);//将空槽数量-1
P(mutex);//进入临界区
将生成的数据放入缓冲区;
V(mutex);//离开临界区
V(fullBuffers);//将内容物个数+1
}
}
void consumer(){
while(TRUE){
P(fullBuffers);//内容物个数-1
P(mutex);
从缓冲区读取数据;
V(mutex);
V(emptyBuffers);//空槽数量+1
}
}
内存不共享的基于Actor模型的TaskPool并发能力模拟:
import { taskpool } from '@kit.ArkTS';
// 跨线程并发任务
@Concurrent
async function produce(): Promise<number>{
// 添加生产相关逻辑
console.log("producing...");
return Math.random();
}
class Consumer {
public consume(value : Object) {
// 添加消费相关逻辑
console.log("consuming value: " + value);
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button() {
Text("start")
}.onClick(() => {
//此处为并发逻辑
let produceTask: taskpool.Task = new taskpool.Task(produce);
let consumer: Consumer = new Consumer();
for (let index: number = 0; index < 10; index++) {
// 执行生产异步并发任务
taskpool.execute(produceTask).then((res : Object) => {
consumer.consume(res);
}).catch((e : Error) => {
console.error(e.message);
})
}
})
.width('20%')
.height('20%')
}
.width('100%')
}
.height('100%')
}
}
可以看到,TaskPool能力相较于传统共享内存下并发开发的优势在于各线程内存独立,因此不需要对共享资源区的操作加锁,TaskPool线程之间通过序列化信息通讯,生产者线程将信息序列化发送给UI线程,UI线程消费结果后将生产任务发送给生产者,对线程的调度在TaskPool内部实现了封装
TaskPool支持开发者在主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程。