【鸿蒙开发api12】——带你彻底理解并发能力(二)

多线程通信

同语言线程间通信(ArkTS内)

ArkTS线程指的是包含ArkTS运行环境的线程,包括主线程、TaskPool线程、Worker线程。它们之间可以通过不同的接口进行通信。

常见业务场景具体业务描述
宿主线<-> TaskPool线程通过使用TaskPool,分发任务到子线程。TaskPool子任务与其宿主线之间需要通信的场景
宿主线<-> Worker线程通过使用Worker,启动子线程,执行任务。Worker子线程与其宿主线之间需要通信的场景
任意线<-> 任意线程除了上述两种线程外,其他任意两个JS线程需要通信的场景

实现方案介绍:

跨线程交互场景通信方式
宿主线程->TaskPool线程参数传递后分发任务;过程中不支持正向通信
TaskPool线程->宿主线程结果返回;sendData触发宿主线程序步回调
宿主线程->Worker线程采用postMessage&onmessage异步通信
Worker线程->宿主线程采用postMessage & onmessage异步通信
任意线程<->任意线程使用@ohos.emitter实现双向异步通信

宿主线程->TaskPool线程

// TaskPool线程
@Concurrent
function print(str: string): void {
    console.log(str);
}

//第一个参数是被@Concurrent修饰的function,后面的参数是funciton的参数
let task = new taskpool.Task(print, "test");
let res = taskpool.execute(task);

TaskPool线程->宿主线程

  • sendData触发宿主线程异步回调
  • 结果返回
@Concurrent 
function sendDataTest(num: number): number { 
    let res: number = num * 10; 
    //sendData 在任务执行过程中向宿主线程发送消息并触发回调,通过onReceiveDate执行回调
    taskpool.Task.sendData(res); 
    return num;
}

function printLog(data: number): void {
    console.info("taskpool data is:" + data);
}

//结果返回 taskpool调用结果以promise的方式返回,可以使用await获取结果
let task: taskpool.Task = new taskpool.Task(sendDataTest, 1);
task.onReceiveData(printLog);

let res = await taskpool.execute(task);
console.log("taskpool execute res is", res)

宿主线程->Worker线程

采用postMessage&onmessage异步通信

// index.tsx
const workerInstance = new worker.ThreadWorker("entry/ets/workers/Worker.ets");
let str = 'test worker';
//postMessage 宿主线程通过拷贝数据的方式向Worker线程发送消息
workerInstance.postMessage(str);

// entry/ets/workers/Worker.ets
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
//onMessage Worker接受消息
workerPort.onmessage = (e: MessageEvents) => {
    let str: string = e.data;
    console.log('worker print', str)
}

Worker线程->宿主线程

采用postMessage&onmessage异步通信

// index.ts
const workerInstance = new Worker.ThreadWorker("entry/ets/workers/Worker.ets");
let str = 'test worker';
workerInstance.postMessage(str);
workerInstance.onmessage = (e: MessageEvents): void => {//onMessage 宿主线程接受消息回调
    console.log(e.data);
}

// entry/ets/workers/Worker.ets
workerPort.onmessage = (e: MessageEvents) => {//postMessage Worker向宿主线程发送消息
    let str: string = e.data;
    console.log(`worker print`, str)
    workerPort.postMessage(`worker return` + str)
}

任意线程->任意线程

使用@ohos.emitter实现双向异步通信

//index.ejs
let innerEvent: emitter.InnerEvent = {
    eventId: 1
};
//Emitter.on 持续订阅指定的事件,并在接受到该事件时,执行对应的回调处理函数
emitter.on(innerEvent, (e) => {
    console.info('emitter result is ', JSON.stringify(e.data));
});

//entry/ets/workers/Worker.ets
let eventData: emitter.EventData = { data: 'worker data' };

let innerEvent: emitter.InnerEvent = {
    eventId: 1,
    priority: emitter.EventPriority.HIGH
};
//Emitter.emit 发送指定优先级事件
emitter.emit(innerEvent, eventData);

Sendable

当前的通信方式是一个序列化的方式,这样就存在一个问题,当数据比较大的时候,就会有额外的性能开销。如何解决?

Sendable机制可以解决这个问题。

默认情况下,Sendable数据在ArkTS并发实例间(包括主线程、TaskPool&Worker工作线程)传递的行为是引用传递,被分配在共享堆SharedHeap中;非Sendable数据分配在私有堆LocalHeap中,如Sendable与SharedHeap示意图所示。同时,ArkTS支持Sendable数据在ArkTS并发实例间的拷贝传递

image-20240926123703747image-20240926123858121

Sendable的使用

在类名上面添加一个@Sendable。

Sendable协议定义了ArkTS的可共享对象体系及其规格约束。符合Sendable协议的数据可以在ArkTS并发实例间传递。

注意事项

@Sendable 类装饰器说明
装饰器参数无。
使用场景限制仅支持在Stage模型的工程中使用。仅支持在.ets文件中使用。
装饰的类继承关系限制Sendable class只能继承Sendable class,普通Class不可以继承Sendable class
装饰的对象内的属性类型限制1.支持string、number、boolean、bigint、null、undefined、Sendable class、collections下面的容器。2.禁止使用闭包变量。3.不支持#define私有属性,需用private。4.不支持计算属性。
装饰的对象内的属性的其他限制成员属性必须显式声明类型、必须显式初始化。成员属性不能跟感叹号。
装饰的对象内的方法参数限制允许使用local变量、入参和通过import引入的变量。禁止使用闭包变量。
Sendable Class的限制不支持增加属性、不支持删除属性、允许修改属性,修改前后属性的型必须一致、不支持修改方法。
适用场景1.在TaskPool或Worker中使用类方法。2.传输对象数据量较大的使用场景。

Seadable线程间通信

1.宿主线程->TaskPool线程

实现方式:参数传递后分发任务(过程中不支持正向通信)

@Sendable 
class Item {
    name: string = "";
    //并发场景下高性能数据传递容器集
    data: collections.Map<string, string> = new collections.Map();
}

@Concurrent 
function executeTask(item: Item) { 
    console.log(util.getHash(item).toString());
}

/*
使用Util.getHash()获取对象的Hash值
如果是第一次获取,则计算Hash值并保存到对象的Hash域(返回随机的Hash值);
如果不是第一次获取,则从Hash域中获取并返回Hash值(同一对象多次返回值保持不变)。
*/
let item = new Item(); 
console.log(util.getHash(item).toString()); 
await taskpool.execute(executeTask, item);
//输出结果两个值相同,内存共享了

2.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));
});
//taskpool set hash is  1411482720
//onReceiveData data hash is  1411482720
//taskpool execute result hash is  1411482720

3.宿主线程->worker线程

采用postMessageWithSharedSendable&onmessage异步通信

// 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
//onMessage worker接受消息
workerPort.onmessage = (e: MessageEvents) => {
    console.log('worker data hash is', util.getHash(e.data));
}
//输出的结果相同

4.Worker线程->宿主线程

和上面类似,只不过是worker线程postMessageWithSharedSendable,宿主线程接受消息

5.任意线程<->任意线程

//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.ts
taskpool.execute(func, Singleton.getInstance())

@Concurrent
function func(instance: Singleton) {
    console.log("是否相等:" + (Singleton.getInstance() === instance))
}
//运行结果:是否相等:fasle

为什么还是false(不加use shared),因为taskpool底层是worker封装的,因此在同一个类中,主线程和worker线程,都有各自的运行环境,相当于有两个模块,所以肯定是false。

所以到底如何实现呢,使用use shared,在import和类之间添加,结果显示true

使用“use shared”这一指令来标记一个模块是否为共享模块,共享模块是进程内只会加载一次的模块

异步锁

当在不同线程对同一个变量进行操作时,会造成线程不安全的问题,解决方法如下:

  1. 使用ArkTSUtils.locks
    为了解决多并发实例间的数据竞争问题,ArkTS语言基础库引入了异步锁能力。
  2. 使用lockAsync加锁
    在获取的锁下独占执行操作。该方法首先先获取锁,然后调用回调,最后释放锁。回调在调用lockAsync的同一线程中以异步方式执行。
import { ArkTSUtils } from '@kit.ArkTS';

"use shared"

@Sendable
export class SingletonA {
    private count_: number = 0;
    lock_: ArkTSUtils.locks.AsyncLock = new ArkTSUtils.locks.AsyncLock();

    //省略getInstance
    public async getCount(): Promise<number> {
        return this.lock_.lockAsync(() => {
            return this.count_;
        });
    }

    public async increaseCount() {
        await this.lock_.lockAsync(() => {
            this.count_++;
        });
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值