鸿蒙应用开发时会涉及到操作系统中关于进程、线程、并发和并行等概念,下面我做了总结:

  1. 进程(Process)

    • 进程是操作系统进行资源分配和调度的单位。每个进程都拥有独立的地址空间,一个进程崩溃后,不会影响到其他进程。

    • 在鸿蒙操作系统中,应用通常作为一个或多个进程运行,每个进程都有自己的内存空间和系统资源。

  2. 线程(Thread)

    • 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。它可以与同属一个进程的其他线程共享进程所拥有的全部资源。

    • 在鸿蒙应用开发中,开发者可以通过创建线程来执行耗时操作,以避免阻塞主线程(UI线程),从而保证应用的流畅性。

  3. 并发(Concurrency)

    • 并发是指系统能够处理多个任务的能力,这些任务在时间上可以是重叠的,但并不一定是在同一时刻发生的。并发可以是顺序执行,也可以是交替执行。在并发执行中,任务的切换可以非常快速,给人一种同时执行的错觉。

    • 在鸿蒙应用中,开发者可以利用并发来提高应用的响应性和效率,例如,在子线程中进行网络请求或数据处理,同时主线程可以继续响应用户操作。

  4. 并行(Parallelism)

    • 并行是指同时执行,例如多线程在多核处理器上同时执行,真正的同时执行。

    • 在鸿蒙应用开发中,如果设备是多核处理器,开发者可以通过并行编程充分利用多核处理器的计算能力,加快任务的执行速度。

一、关于进程

1.1、分类

分成主进程与其它的一些独立进程

  • 主进程(Main Process):应用中(同一Bundle名称)的所有UIAbility、ServiceExtensionAbility和DataShareExtensionAbility均是运行在同一个独立进程(主进程)中,如下图中绿色部分的“Main Process”。

  • 独立进程(xxx Process):应用中(同一Bundle名称)的所有同一类型ExtensionAbility(除ServiceExtensionAbility和DataShareExtensionAbility外)均是运行在一个独立进程中,如下图中蓝色部分的“FormExtensionAbility Process”、“InputMethodExtensionAbility Process”、其他ExtensionAbility Process。WebView拥有独立的渲染进程,如下图中黄色部分的“Render Process”。

image.png

基于当前的进程模型,针对应用间和应用内存在多个进程的情况,系统提供了如下进程间通信机制: 公共事件机制:多用于一对多的通信场景,公共事件发布者可能存在多个订阅者同时接收事件。 可以通过commonEventManager进行进程间通信- 公共事件发布 + 自定义事件发布。

image.png

1.2、关于跨进程、线程、应用等的数据通信的方案总结:

eventHub同UIAbility通信(Ability Kit API) :

EventHub模块提供了事件中心,提供订阅、取消订阅、触发事件的能力。EventHub不是全局的事件中心,不同的context对象拥有不同的EventHub对象,事件的订阅、取消订阅、触发都作用在某一个具体的EventHub对象上,因此不能用于虚拟机间或者进程间的事件传递。

  • eventHub.emit({eventId:xx},数据) :发布订阅

  • eventHub.on({eventId:xx},()=>{}) :订阅事件

  • eventHub.off({eventId:xx},()=>{}) :取消订阅

emitter跨线程通信(Basic Services Kit API):

本模块提供了在同一进程不同线程间,或同一进程同一线程内,发送和处理事件的能力,包括持续订阅事件、单次订阅事件、取消订阅事件,以及发送事件到事件队列的能力,用于TaskPool开辟的子线程直接的通信。

  • emitter.emit({eventId:xx},数据) :发布订阅

  • emitter.on({eventId:xx},()=>{}) :订阅事件

  • emitter.off({eventId:xx},()=>{}) :取消订阅

  • emitter.once({eventId:xx},()=>{}) :单次订阅事件,执行一次自动取消订阅

Ability之间的信息传递

Want是对象间信息传递的载体, 可以用于应用组件间的信息传递。 Want的使用场景之一是作为startAbility的参数, 当Ability A需要启动Ability B并传入一些数据时, 可使用Want作为载体将这些数据传递给Ability B。

I/RPC跨进程通信(IPC Kit API):

IPC(Inter-Process Communication)与RPC(Remote Procedure Call)用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,后者使用软总线驱动,用于跨设备跨进程通信。需要跨进程通信的原因是因为每个进程都有自己独立的资源和内存空间,其他进程不能随意访问不同进程的内存和资源,IPC/RPC便是为了突破这一点。

说明: Stage模型不能直接使用本文介绍的IPC和RPC,需要通过以下能力实现相关业务场景:

IPC典型使用场景在后台服务,应用的后台服务通过IPC机制提供跨进程的服务调用能力。

RPC典型使用场景在多端协同,多端协同通过RPC机制提供远端接口调用与数据传递能力。

dataShare跨应用通信:

用于应用管理其自身数据,同时支持同个设备上不同应用间的数据共享。

二、关于线程

2.1、分类:

Stage模型下的线程主要有如下三类:

  • 主线程

    • 执行UI绘制。

    • 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。

    • 管理其他线程的ArkTS引擎实例,例如使用TaskPool任务池创建任务或取消任务、启动和终止Worker线程。

    • 分发交互事件。

    • 处理应用代码的回调,包括事件处理和生命周期管理。

    • 接收TaskPool以及Worker线程发送的消息。

  • TaskPool Worker线程

    • 用于执行耗时操作,支持设置调度优先级、负载均衡等功能,推荐使用。

    • 没有线程数量限制-由系统优先级调度

  • Worker线程

    • 最多8个

    • 用于执行耗时操作,支持线程间通信。TaskPool与Worker的运作机制、通信手段和使用方法可以参考TaskPool和Worker的对比。

​​​​​​​

2.2、多线程的taskPool和worker之间的区别及使用场景,实际项目怎么使用?

实现TaskPoolWorker
内存模型线程间隔离,内存不共享。线程间隔离,内存不共享。
参数传递机制采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。
参数传递直接传递,无需封装,默认进行transfer。消息对象唯一参数,需要自己封装。
方法调用直接将方法传入调用。在Worker线程中进行消息解析并调用对应方法。
返回值异步调用后默认返回。主动发送消息,需在onmessage解析赋值。
生命周期TaskPool自行管理生命周期,无需关心任务负载高低。开发者自行管理Worker的数量及生命周期。
任务池个数上限自动管理,无需配置。同个进程下,最多支持同时开启8个Worker线程。
任务执行时长上限无限制。无限制。
设置任务的优先级不支持。不支持。
执行任务的取消支持取消任务队列中等待的任务。不支持。

补充:

  • TaskPool中超长任务(大于3分钟)会被系统自动回收,如何避免超长任务?将超大数据进行分段处理。

  • Worker:Worker中不能直接更新Page。Worker是否配置文件:在工程的模块级build-profile.json5文件的buildOption属性中添加配置信息。

  • taskPool与worker参数传递中数据类型支持:

    基本类型数据、传递通过自定义class创建出来的object时,不会发生序列化错误

  • Worker最多管控8个线程

    TaskPool(任务池)和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗,并提高系统的整体性能。

常见的一些开发场景及适用具体说明如下:
  1. 有关联的一系列同步任务。例如在一些需要创建、使用句柄的场景中,句柄创建每次都是不同的,该句柄需永久保存,保证使用该句柄进行操作,需要使用Worker

  2. 需要频繁取消的任务。例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool

  3. 大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用8个Worker去做负载管理,推荐采用TaskPool

2.3、子线程和主线程的消息传递

Worker开辟的子线程需要配合postMessage和onMessage实现消息传递;

TaskPool开辟的子线程通过回调传递进行传递;对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等是通过Emitter发送和处理事件的。

  • 子线程传递给主线程一个方法,是否支持?

    不支持,自定义class的属性(如Function)无法通过序列化传递。

        let func1 = function() {
            console.log("post message is function");
        }
        // workerPort.postMessage(func1); 传递func1发生序列化错误

4、拓展其它问题

4.1、主线程上的数据对象序列化后传到子线程,子线程改变了对象的属性值,主线程跟着一起改变吗?

不会,序列化是转换为字节码,已经和之前的对象没有关系了,就算反序列化成对象也是一个新对象。

4.2、鸿蒙系统http在接口请求时,系统本身应该是开启子线程来请求,那么为什么在项目中还要使用taskpool开辟子线程呢?

鸿蒙底层的http请求实现是在子线程中进行的。但在http在接口请求时,我们再开辟的子线程主要针对于请求参数的编码与返回值的解析,这部分需要花费时间来处理的业务逻辑,因为在返回大量时值解析是比较花费时间的,所以需要使用TaskPool开辟子线程。在多任务请求时TaskPool开辟的子线程可以执行密集型任务的开发,更加的灵活方便。

本文主要的参考文献:

 OpenHarmony官方文档

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐