多线程通信
同语言线程间通信(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并发实例间的拷贝传递
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”这一指令来标记一个模块是否为共享模块,共享模块是进程内只会加载一次的模块
异步锁
当在不同线程对同一个变量进行操作时,会造成线程不安全的问题,解决方法如下:
- 使用ArkTSUtils.locks
为了解决多并发实例间的数据竞争问题,ArkTS语言基础库引入了异步锁能力。 - 使用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_++;
});
}
}