鸿蒙开发-多线程并发

333 阅读8分钟

1.多线程并发概述

并发模型就像是为不同的应用场景准备的不同“做事方法”。

有两种常见类型,一种是基于内存共享,一种是基于消息通信

Actor 并发模型是基于消息通信这种类型里的代表。用它的话,开发者不用头疼锁带来的那些复杂又偶然出现的问题,而且并发程度比较高,所以很多人都喜欢用。

现在 ArkTS 有 TaskPool 和 Worker 这两种并发能力,它们都是根据 Actor 并发模型来实现的。简单来说,TaskPoolWorker 能让应用程序在多线程环境下更高效地运行。

TaskPoolWorker
创建方式无特定创建方式说明有手动和自动两种方式,手动需同步配置
文件路径规则无特定说明构造函数中传入的 Worker 线程文件路径在不同版本有不同规则
生命周期管理无需关心线程实例生命周期需手动管理生命周期,最多同时运行 64 个子线程
装饰器要求任务函数用 @Concurrent 装饰器标注,API version 11 起跨并发实例传递带方法的实例对象用 @Sendable 装饰器标注,均仅在.ets 文件中使用无此特定装饰器要求
执行耗时限制在 TaskPool 工作线程执行耗时不能超 3 分钟(不包括 Promise 和 async/await 的 I/O 任务耗时),否则强制退出无明确提及类似耗时限制
参数类型任务函数入参需是序列化支持的类型任务函数入参需是序列化支持的类型
ArrayBuffer 处理默认转移,可通过 setTransferList () 设置转移列表无明确提及类似处理方式
库的使用限制只能用线程安全库,不能用 UI 相关非线程安全库只能用线程安全库,不能用 UI 相关非线程安全库
数据量限制序列化传输数据量大小为 16MB 限制序列化传输数据量大小为 16MB 限制
优先级说明Priority 的 IDLE 优先级标记后台耗时任务,优先级最低,仅在所有线程空闲时用一个线程执行无明确提及类似优先级说明
Promise 传递不支持跨线程传递,对不同状态 Promise 不同处理无明确提及类似 Promise 传递说明
AppStorage 使用限制工作线程不能用 AppStorage工作线程不能用 AppStorage

2.TaskPool

概念

任务池(TaskPool)作用是为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能,并且无需关心线程实例的生命周期。

用通俗易懂的话来讲讲任务池

想象一下你有很多家务要做,比如扫地、洗碗、擦桌子等等。如果只有你一个人做,一件一件按顺序来,那可能会花很长时间,而且你会很累。但如果有一群人帮你一起做这些家务呢?任务池就像是这个 “一群人”。

在应用程序里,有很多任务要处理,比如计算数据、发送请求、处理文件等。如果一个一个地按顺序处理,就会很慢,还可能让你的电脑或手机变得很卡。任务池就是把这些任务收集起来,然后分配给多个 “小助手”(线程)同时去做。这样一来,任务完成得快多了,就像一群人一起做家务效率更高一样。

而且呢,你不用操心这些 “小助手” 啥时候开始干活、啥时候结束,任务池会自动管理它们的生命周期。你只需要把任务交给任务池,然后等着结果就行啦。这样就降低了整体资源的消耗,让你的应用程序运行得更顺畅,性能也更好。

运行机制图

image.png

注意事项

一、装饰器要求

  1. 任务函数用 @Concurrent 装饰器标注,仅在.ets 文件中使用。
  2. API version 11 起,跨并发实例传递带方法的实例对象用 @Sendable 装饰器标注,也仅在.ets 文件中使用。

二、执行耗时限制
任务函数在 TaskPool 工作线程执行耗时不能超 3 分钟(不包括 Promise 和 async/await 的 I/O 任务耗时),否则强制退出。

三、参数类型
任务函数入参需是序列化支持的类型。

四、ArrayBuffer 处理
ArrayBuffer 参数在 TaskPool 中默认转移,可通过 setTransferList () 设置转移列表。

五、库的使用限制
TaskPool 工作线程只能用线程安全库,不能用 UI 相关非线程安全库。

六、数据量限制
序列化传输数据量大小为 16MB 限制。

七、优先级说明
Priority的IDLE 优先级标记后台耗时任务,优先级最低,仅在所有线程空闲时用一个线程执行。

八、Promise 传递
Promise 不支持跨线程传递,TaskPool 对不同状态 Promise 不同处理。

九、AppStorage 限制
TaskPool 工作线程不能用 AppStorage。

@Concurrent装饰器

注意

由于@Concurrent标记的函数不能访问闭包,因此@Concurrent标记的函数内部不能调用当前文件的其他函数

image.png

基本使用

1.并发函数:使用 @Concurrent 装饰器定义并发函数。

2.任务创建:使用 taskpool.Task 创建任务,传入并发函数和参数。

3.任务执行:使用 taskpool.execute 执行任务,并处理结果或捕获异常。

import { taskpool } from '@kit.ArkTS';

/**
 * 计算两个数字的和。
 * 该函数被标记为 @Concurrent 装饰器,表示它可以并发执行。
 *
 * @param num1 第一个数字
 * @param num2 第二个数字
 * @returns 两个数字的和
 */
@Concurrent
function add(num1: number, num2: number): number {
  return num1 + num2;
}

/**
 * 异步执行通过任务池的 add 函数。
 * 该函数演示了如何使用任务池执行并发任务并处理结果。
 */
async function ConcurrentFunc(): Promise<void> {
  try {
    // 创建并执行任务
    const result = await taskpool.execute(new taskpool.Task(add, 1, 2));
    console.info(`taskpoolres : ${result}`);
  } catch (e) {
    // 捕获并记录任务执行过程中发生的任何错误
    console.error(`taskpoolerror: ${e}`);
  }
}
@Entry
@Component
struct Index {
  build() {
    Column() {
      Button('taskpool')
        .onClick(()=>{
          ConcurrentFunc();
        })
    }
    .width('100%')
    .height('100%')
  }
}

3.Worker

概念

Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与主线程分离,在后台线程中运行一个脚本进行耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞主线程的运行。

用通俗易懂的话来讲讲Worker

假设你正在玩一个游戏,主线程就像是你在游戏中直接操作的画面,比如控制角色移动、打怪等。但是有时候游戏里会有一些很费时间的事情要做,比如加载一个特别大的地图或者进行复杂的计算。如果让主线程来做这些事,就像你在玩游戏的时候突然卡住不动了,这多让人着急呀。

而 Worker 就像是一个在后台默默干活的小助手。当有那些耗时的操作时,比如计算密集型任务或者要等很久才有结果的任务,就把这些任务交给 Worker 去做。这样主线程(你玩游戏的画面)就不会被堵住,可以继续流畅地进行。等 Worker 在后台把任务完成了,再把结果告诉主线程,就像小助手默默地把困难的任务完成了,不影响你玩游戏的体验。

总之,Worker 就是为了让应用程序在运行的时候,把那些费时间的任务放到后台去做,不影响主线程的正常运行,让整个应用程序更加顺畅。

运行机制图

image.png

注意事项

一、创建方式

创建 Worker 有手动和自动两种方式,手动创建需同步进行相关配置,参考创建注意事项。

二、文件路径规则
使用 Worker 能力时,构造函数中传入的 Worker 线程文件路径在不同版本有不同规则,参见文件路径注意事项。

三、生命周期管理

  1. Worker 创建后需手动管理生命周期。
  2. 最多同时运行 64 个 Worker 子线程。

四、库的使用限制
Worker 线程只能用线程安全库,不能用 UI 相关非线程安全库。

五、数据量限制
序列化传输数据量大小为 16MB 限制。

六、异常处理
使用 Worker 模块时,需在主线程注册 onerror 接口,否则 Worker 线程出现异常会发生 jscrash 问题。

七、跨 HAP 使用限制
不支持跨 HAP 使用 Worker 线程文件。

八、文件加载限制

  1. 创建 Worker 对象仅允许加载本模块下的 Worker 线程文件。
  2. 若依赖其他模块的 Worker 功能,需将整套逻辑封装到方法中导出使用。

九、引用 HAR/HSP 要求
引用 HAR/HSP 前需先配置依赖。

十、AppStorage 使用限制
不支持在 Worker 工作线程中使用 AppStorage。

创建worker

手动创建

在build-profile.json5配置

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

并在/src/main/ets/workers/中创建worker.ets

自动创建

image.png

image.png

image.png