鸿蒙多线程开发——sendable共享容器
1、异步锁机制
在介绍共享容器之前,先介绍异步锁机制。
为了解决多线程并发任务间的数据竞争问题,ArkTS引入了异步锁能力。异步锁可能会被类对象持有,因此为了更方便地在并发实例间获取同一个异步锁对象,AsyncLock对象支持跨线程引用传递。
由于ArkTS语言支持异步操作,阻塞锁容易产生死锁问题,因此在ArkTS中仅支持异步锁(非阻塞式锁)。同时,异步锁还可以用于保证单线程内的异步任务时序一致性,防止异步任务时序不确定导致的同步问题。
使用异步锁的方法需要标记为async,调用方需要使用await修饰,才能保证时序正确。 |
为了解决@Sendable共享对象在不同线程修改共享变量导致的竞争问题,可以采用异步锁进行数据保护。Sendable共享对象在之前文章中有介绍👉🏻鸿蒙多线程开发——线程间数据通信对象03(sendable)
异步锁使用示例如下(关注9~11行,15~17行):
import { ArkTSUtils, taskpool } from '@kit.ArkTS';
@Sendable
export class A {
private count_: number = 0;
lock_: ArkTSUtils.locks.AsyncLock = new ArkTSUtils.locks.AsyncLock();
public async getCount(): Promise<number> {
return this.lock_.lockAsync(() => {
return this.count_;
})
}
public async increaseCount() {
await this.lock_.lockAsync(() => {
this.count_++;
})
}
}
@Concurrent
async function printCount(a: A) {
console.info("InputModule: count is:" + await a.getCount());
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(async () => {
let a: A = new A();
await taskpool.execute(printCount, a);
})
}
.height('100%')
.width('100%')
}
}
2、共享容器
ArkTS共享容器(@arkts.collections (ArkTS容器集))是一种在并发任务间共享传输的容器类,可以用于并发场景下的高性能数据传递。
ArkTS共享容器在多个并发任务间传递时,其默认行为是引用传递,支持多个并发任务可以操作同一个容器实例。另外,也支持拷贝传递,即每个并发任务持有一个ArkTS容器实例。
ArkTS共享容器并不是线程安全的,内部使用了fail-fast(快速失败)机制,即当检测多个并发实例同时对容器进行结构性改变时,会触发异常。因此,在容器内修改属性的场景下,我们需要使用ArkTS提供的异步锁机制保证ArkTS容器的安全访问。
ArkTS共享容器包含如下几种:Array、Map、Set、TypedArray(Int8Array、Uint8Array、Int16Array、Uint16Array、Int32Array、Uint32Array、Uint8ClampedArray、Float32Array)、ArrayBuffer等。
📢 注意,这里说的容器是@arkts.collections中创建的ArkTs容器(多线程共享容器),并非平时普通使用的容器。 |
一个Array容器的使用案例如下:
import { ArkTSUtils, collections, taskpool } from '@kit.ArkTS';
@Concurrent
async function add(arr: collections.Array<number>, lock: ArkTSUtils.locks.AsyncLock) {
await lock.lockAsync(() => { // 如果不添加异步锁,任务会因为数据竞争冲突,导致抛异常失败
arr[0]++;
})
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
let taskGroup = new taskpool.TaskGroup();
let lock = new ArkTSUtils.locks.AsyncLock();
let arr = collections.Array.create<number>(1, 0);
let count = 1000;
while (count--) {
taskGroup.addTask(add, arr, lock);
}
taskpool.execute(taskGroup).then(() => {
console.info(`Return success: ${arr[0]} === ${count}`);
}).catch((e: Error) => {
console.error("Return error.");
})
})
}
.height('100%')
.width('100%')
}
}
3、共享容器注意事项
在@arkts.collections (ArkTS容器集)中提供的容器与前端开发中JS所使用的容器大体上保持一致。但有一部分存在差异。下面对这些差异做一些归类。
📢📢 注意,这里说的容器是@arkts.collections中创建的ArkTs容器(多线程共享容器),并非平时普通使用的容器。 |
有差异的有:Array、Map、Set、TypedArray。(ArrayBuffer没有差异)
其中TypedArray是以下几种类型的统称:
-
-
Int8Array、Uint8Array、Uint8ClampedArray
-
Int16Array、Uint16Array
-
Int32Array、Uint32Array、Float32Array
-
3.1、Array与JS原生API的差异
支持原生容器Array通过collections.Array.from方法转换为ArkTS Array容器;支持通过原生容器Array的from方法将ArkTS Array容器转换为原生容器Array。
有差异的部分罗列如下:
原生API方法 | ArkTS容器集方法 | 在ArkTS容器中的差异表现 |
length: number | readonly length: number | 为了防止undefined扩散,不允许设置length。 |
new(arrayLength ?: number): any[] | static create(arrayLength: number, initialValue: T): Array | 为了防止undefined扩散,构造函数中必须提供一个初始值的构造函数。 |
new <T>(...items: T[]): T[] | constructor(first: T, ...left: T[]) | 为了防止undefined扩散,构造函数中必须提供一个初始值的构造函数,继承场景下,无法调用该函数进行对象构造。 |
pop(): T | undefined | pop(): T | undefined | 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。 |
push(...items: T[]): number | push(...items: T[]): number | 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。 |
concat(...items: ConcatArray<T>[]): T[] | concat(...items: ConcatArray<T>[]): Array<T> | 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。 |
concat(...items: (T | ConcatArray<T>)[]): T[] | concat(...items: ConcatArray<T>[]): Array<T> | 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。 |
shift(): T | undefined | shift(): T | undefined | 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。 |
sort(compareFn?: (a: T, b: T) => number): this | sort(compareFn?: (a: T, b: T) => number): Array<T> | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。2. 继承场景下,无法获得实际类型的返回值。 |
unshift(...items: T[]): number | unshift(...items: T[]): number | 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。 |
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void | forEach(callbackFn: (value: T, index: number, array: Array<T>) => void): void | ArkTS不支持this,因此不支持thisArg参数。 |
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[] | map<U>(callbackFn: (value: T, index: number, array: Array<T>) => U): Array<U> | ArkTS不支持this,因此不支持thisArg参数。 |
filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[] | filter(predicate: (value: T, index: number, array: Array<T>) => boolean): Array<T> | ArkTS不支持this,因此不支持thisArg参数。 |
[n: number]: T | [index: number]: T | 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。 |
findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number | findIndex(predicate: (value: T, index: number, obj: Array<T>) => boolean): number | ArkTS不支持this,因此不支持thisArg参数。 |
fill(value: T, start?: number, end?: number): this | fill(value: T, start?: number, end?: number): Array<T> | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作,否则会抛出异常。2. 继承场景下,无法获得实际类型的返回值。 |
3.2、TypedArray与JS原生API的差异
支持原生容器TypedArray通过collections.TypedArray.from方法转换为ArkTS TypedArray容器;
支持通过原生容器TypedArray的from方法将ArkTS TypedArray容器转换为原生容器TypedArray。
以Int8Array为例,有差异部分罗列如下:
原生API方法 | ArkTS容器集方法 | 在ArkTS容器中的差异表现 |
copyWithin(target: number, start: number, end?: number): this | copyWithin(target: number, start: number, end?: number): Int8Array | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
every(predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any): boolean | every(predicate: TypedArrayPredicateFn<number, Int8Array>): boolean | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. ArkTS不支持this,因此不支持thisArg参数。 |
fill(value: number, start?: number, end?: number): this | fill(value: number, start?: number, end?: number): Int8Array | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
filter(predicate: (value: number, index: number, array: Int8Array) => any, thisArg?: any): Int8Array | filter(predicate: TypedArrayPredicateFn<number, Int8Array>): Int8Array | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. ArkTS不支持this,因此不支持thisArg参数。 |
find(predicate: (value: number, index: number, obj: Int8Array) => boolean, thisArg?: any): number | undefined | find(predicate: TypedArrayPredicateFn<number, Int8Array>): number | undefined | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. ArkTS不支持this,因此不支持thisArg参数。 |
findIndex(predicate: (value: number, index: number, obj: Int8Array) => boolean, thisArg?: any): number | findIndex(predicate: TypedArrayPredicateFn<number, Int8Array>): number | ArkTS不支持this,因此不支持thisArg参数。 |
forEach(callbackfn: (value: number, index: number, array: Int8Array) => void, thisArg?: any): void | forEach(callbackFn: (value: number, index: number, array: Int8Array) => void): void | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. ArkTS不支持this,因此不支持thisArg参数。 |
map(callbackfn: (value: number, index: number, array: Int8Array) => number, thisArg?: any): Int8Array | map(callbackFn: TypedArrayForEachCallback<number, Int8Array>): Int8Array | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. ArkTS不支持this,因此不支持thisArg参数。 |
set(array: ArrayLike<number>, offset?: number): void | set(array: ArrayLike<number>, offset?: number): void | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
some(predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any): boolean | some(predicate: TypedArrayPredicateFn<number, Int8Array>): boolean | ArkTS不支持this,因此不支持thisArg参数。 |
sort(compareFn?: (a: number, b: number) => number): this | sort(compareFn?: TypedArrayCompareFn<number>): Int8Array | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. 继承场景下,无法获得实际类型的返回值。 |
subarray(begin?: number, end?: number): Int8Array | subarray(begin?: number, end?: number): Int8Array | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
[index: number]: number | [index: number]: number | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
from<T>(arrayLike: ArrayLike<T>, mapfn: (v: T, k: number) => number, thisArg?: any): Int8Array | static from<T>(arrayLike: ArrayLike<T>, mapFn: TypedArrayFromMapFn<T, number>): Int8Array | ArkTS不支持this,因此不支持thisArg参数。 |
from(arrayLike: Iterable<number>, mapfn?: (v: number, k: number) => number, thisArg?: any): Int8Array | static from(arrayLike: Iterable<number>, mapFn?: TypedArrayFromMapFn<number, number>): Int8Array | ArkTS不支持this,因此不支持thisArg参数。 |
3.3、Map与JS原生API的差异
差异部分罗列如下:
原生API方法 | ArkTS容器集方法 | 在ArkTS容器中的差异表现 |
readonly size: number | readonly size: number | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
clear(): void | clear(): void | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
delete(key: K): boolean | delete(key: K): boolean | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void | forEach(callbackFn: (value: V, key: K, map: Map<K, V>) => void): void | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. ArkTS不支持this,因此不支持thisArg参数。 |
get(key: K): V | undefined | get(key: K): V | undefined | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
has(key: K): boolean | has(key: K): boolean | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
set(key: K, value: V): this | set(key: K, value: V): Map<K, V> | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
new <K, V>(entries?: readonly (readonly [K, V])[] | null): Map<K, V> | constructor(entries?: readonly (readonly [K, V])[] | null) | 构造时传入的k,v键值不能是非Sendable数据,否则编译会报错。 |
3.4、Set与JS原生API的差异
差异部分罗列如下:
原生API方法 | ArkTS容器集方法 | 在ArkTS容器中的差异表现 |
readonly size: number | readonly size: number | Sendable类和接口中不允许使用计算属性名称(arkts-sendable-compated-prop-name)。 |
add(value: T): this | add(value: T): Set<T> | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
clear(): void | clear(): void | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
delete(value: T): boolean | delete(value: T): boolean | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void | forEach(callbackFn: (value: T, value2: T, set: Set<T>) => void): void | 1. 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。2. ArkTS不支持this,因此不支持thisArg参数。 |
has(value: T): boolean | has(value: T): boolean | 不允许在遍历、访问过程中进行元素的增、删、改操作否则会抛出异常。 |
values(): IterableIterator<T> | values(): IterableIterator<T> | Sendable类和接口中不允许使用计算属性名称(arkts-sendable-compated-prop-name)。 |
new <T = any>(values?: readonly T[] | null): Set<T> | constructor(values?: readonly T[] | null) | 构造时传入数据不能是非Sendable数据,否则编译会报错。 |