6.HarmonyOs中worker 和 taskpool的区别:

当前ArkTS提供了TaskPool和Worker两种并发能力,TaskPool和Worker都基于Actor并发模型实现。

Actor:

0000000000011111111.20240823151656.83734337371538927746298413217146:50001231000000:2800:DD22EB29370266C60344A54994E1E9281B4F0E6470606F66B7A2586B9183F3DD.png
Actor模型不同角色之间并不共享内存,生产者线程和UI线程都有自己独占的内存。生产者生产出结果后通过序>列化通信将结果发送给UI线程,UI线程消费结果后再发送新的生产任务给生产者线程。
Actor并发模型每一个线程都是一个独立Actor,每个Actor有自己独立的内存,Actor之间通过消息传递机制触发>对方Actor的行为,不同Actor之间不能直接访问对方的内存空间。

​ Actor模型对比穿透的内存共享并发模型的优势在于不同线程间内存隔离,不会产生不同线程竞争同一内存资源的问题。开发者不需要考虑对内存上锁导致的一系列功能、性能问题,提升了开发效率。

TaskPool

​ 任务池(TaskPool)作用是为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能,且您无需关心线程实例的生命周期。
​ 在主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程。0000000000011111111.20240823151655.11841796565565036069433485280912:50001231000000:2800:E18C0F3B74A0748FF52C7396CB3B2E7D76EE9B91D531780292E3BE44D62FD908.png

注意事项:

  • 实现任务的函数需要使用装饰器@Concurrent标注,且仅支持在.ets文件中使用。
  • 从API version 11开始,实现任务的函数需要使用类方法时,该类必须使用装饰器@Sendable标注,且仅支持在.ets文件中使用。
  • 任务函数在TaskPool工作线程的执行耗时不能超过3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时),否则会被强制退出。
  • 实现任务的函数入参需满足序列化支持的类型,详情请参见TaskPool和Worker支持的序列化类型
  • ArrayBuffer参数在TaskPool中默认转移,需要设置转移列表的话可通过接口setTransferList()设置。
  • 由于不同线程中上下文对象是不同的,因此TaskPool工作线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。
  • 序列化传输的数据量大小限制为16MB。
  • Priority的IDLE优先级是用来标记需要在后台运行的耗时任务(例如数据同步、备份。),它的优先级别是最低的。这种优先级标记的任务只会在所有线程都空闲的情况下触发执行,并且只会占用一个线程来执行。
  • Promise不支持跨线程传递,不能作为concurrent function的返回值。

Worker:

​ Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与主线程分离,在后台线程中运行一个脚本操作耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞主线程的运行。
​ 创建Worker的线程称为宿主线程(不一定是主线程,工作线程也支持创建Worker子线程),Worker自身的线程称为Worker子线程(或Actor线程、工作线程)。每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等,因此每个Worker启动存在一定的内存开销,需要限制Worker的子线程数量。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。0000000000011111111.20240823151655.99534915378118893386159233363293:50001231000000:2800:B3C2DAB5E146A2AE9A986C42520F7A48C92F0FFF303D2EC6924B1DFB01F08A70.png

注意事项

  • 创建Worker时,有手动和自动两种创建方式,手动创建Worker线程目录及文件时,还需同步进行相关配置,详情请参考创建Worker的注意事项
  • Worker创建后需要手动管理生命周期,且最多同时运行的Worker子线程数量为64个。
  • 由于不同线程中上下文对象是不同的,因此Worker线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。
  • 序列化传输的数据量大小限制为16MB。
  • 使用Worker模块时,需要在主线程中注册onerror接口,否则当worker线程出现异常时会发生jscrash问题。
  • 不支持跨HAP使用Worker线程文件。
  • 创建Worker对象时仅允许加载本模块下存在的worker线程文件,不支持加载其他模块的worker线程文件。若依赖其他模块提供的Worker功能,需要将Worker实现的整套逻辑封装到方法中,将方法导出后供其他模块使用。
  • 引用HAR/HSP前,需要先配置对HAR/HSP的依赖,详见引用共享包
  • Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。

TaskPool和Worker对比

实现 TaskPool Worker
内存模型 线程间隔离,内存不共享。 线程间隔离,内存不共享。
参数传递机制 采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。 采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。
参数传递 直接传递,无需封装,默认进行transfer。 消息对象唯一参数,需要自己封装。
方法调用 直接将方法传入调用。 在Worker线程中进行消息解析并调用对应方法。
返回值 异步调用后默认返回。 主动发送消息,需在onmessage解析赋值。
生命周期 TaskPool自行管理生命周期,无需关心任务负载高低。 开发者自行管理Worker的数量及生命周期。
任务池个数上限 自动管理,无需配置。 同个进程下,最多支持同时开启64个Worker线程,实际数量由进程内存决定。
任务执行时长上限 3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时),长时任务无执行时长上限。 无限制。
设置任务的优先级 支持配置任务优先级。 不支持。
执行任务的取消 支持取消已经发起的任务。 不支持。
线程复用 支持。 不支持。
任务延时执行 支持。 不支持。
设置任务依赖关系 支持。 不支持。
串行队列 支持。 不支持。
任务组 支持。 不支持。

适用场景对比

TaskPool和Worker均支持多线程并发能力。由于TaskPool的工作线程会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容),而Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级,故在性能方面使用TaskPool会优于Worker,因此大多数场景推荐使用TaskPool。

TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟且非长时任务)会被系统自动回收;而Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。

常见的一些开发场景及适用具体说明如下:

  • 运行时间超过3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时)的任务。例如后台进行1小时的预测算法训练等CPU密集型任务,需要使用Worker。
  • 有关联的一系列同步任务。例如在一些需要创建、使用句柄的场景中,句柄创建每次都是不同的,该句柄需永久保存,保证使用该句柄进行操作,需要使用Worker。
  • 需要设置优先级的任务。例如图库直方图绘制场景,后台计算的直方图数据会用于前台界面的显示,影响用户体验,需要高优先级处理,需要使用TaskPool。
  • 需要频繁取消的任务。例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool。
  • 大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用Worker去做负载管理,推荐采用TaskPool。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐