HarmonyOS 鸿蒙中的多线程
HarmonyOS 鸿蒙中的多线程
学习研究关于鸿蒙的并发的资料,详细可参考官方文档
线程与进程的理解
从一个小例子开始认识进程:就像一张食谱,平常被放在抽屉里,直到厨师拿出来,放在桌上,按步骤做菜才能将食物做出来;一段程序,本身不会执行,程序由程序员写好放在硬盘中永久保存,需要由cpu从硬盘中读取到内存中,方便cpu读取,这个在内存中可读取的实例就叫进程。一个程序多次被读取到内存中,便产生了多个独立的进程,硬盘中多个程序被读取到内存中运行时,也就创建了多个进程。
线程是一个操作系统能够运算调度的最小单位,被包含在进程之中,也是进程中的实际运作单位。而在一个应用中(也就是同一个Bundle名)也许会有很多个进程,但是所有的UIAbility、ServiceExtensionAbility和DataShareExtensionAbility最终还是会运行在同一个主进程中,而同一类型的ExtensionAbility(除ServiceExtensionAbility和DataShareExtensionAbility外)均是运行在一个独立进程中。在鸿蒙开发中,每个进程都有一个主线程。
使用线程可以并发执行任务,提高cpu利用率,但单线程可能导致资源竞争问题,如不同线程更新同一段内存数据时数不一致问题(多个异步调用,数据运行后结果的混乱),为了解决这个问题,需要对线程进行同步,让原先异步的操作依次有序的执行,常用的线程同步机制为锁,但频繁进行枷锁和解锁的操作非常低效。
并发模型
并发的含义:多任务同时进行
模型类别:
基于内存共享的并发模型
特性:
-
原子性:指一个操作是不可中断的,要么全部执行成功要么全部执行失败。
-
有序性:指程序执行的顺序必须符合预期,不能出现乱序的情况。
-
可见性:指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。
现代程序语言一般通过锁、内存屏障、原子指令来满足这三条性质。
基于消息传递的并发模型
CSP:计算单元并不能直接互相发送信息。需要通过通道(Channel)作为媒介进行消息传递:发送方需要将消息发送到Channel,而接收方需要从Channel读取消息。默认情况下Channel是没有缓存的,因此对Channel的发送(Send)动作是同步阻塞的,直到另外一个持有该Channel引用的执行块取出消息
Actor:每个Actor可以看做一个独立的计算单元,并且相互之间内存隔离,每个Actor中存在信箱(Mail Box),Actor之间可以直接进行消息传递。Actor需要明确指定消息接收方。Actor模型中信箱本质是队列,因此消息的发送和接收可以是异步的。
arkts就是基于消息传递的模型的程序语言
Stage模型下的线程主要有如下三类:
-
主线程
-
执行UI绘制。
-
管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
-
管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程。
-
分发交互事件。
-
处理应用代码的回调,包括事件处理和生命周期管理。
-
接收TaskPool以及Worker线程发送的消息。
-
-
-
用于执行耗时操作,支持设置调度优先级、负载均衡等功能,推荐使用。
-
-
-
用于执行耗时操作,支持线程间通信。
-
任务相关文档:https://docs.openharmony.cn/pages/v4.1/zh-cn/application-dev/reference/apis-arkts/js-apis-worker.md
TaskPool(任务池)
TaskPool在Worker之上实现了调度器和Worker线程池,TaskPool根据任务的优先级,将其放入不同的优先级队列,调度器会依据自己实现的调度算法(优先级,防饥饿),从优先级队列中取出任务,放入TaskPool中的Worker线程池,执行相关任务
TaskPool有如下的特点:
-
轻量化的并行机制。
-
降低整体资源的消耗。
-
提高系统的整体性能。
-
无需关心线程实例的生命周期。
-
可以使用TaskPool API创建后台任务(Task),并对所创建的任务进行如任务执行、任务取消的操作。
-
根据任务负载动态调节TaskPool工作线程的数量,以使任务按照预期时间完成任务。
-
可以设置任务的优先级。
-
可以设置任务组(TaskGroup)将任务关联起来。
Worker(有线程个数限制:64)ps:在开发者大会之前(api11版本)线程数限制为8
Worker主要作用是为应用程序提供一个多线程的运行环境,可在执行过程中与主线程分离,在后台线程中运行一个脚本操作耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞主线程的运行。
创建Worker的线程称为宿主线程(不一定是主线程,工作线程也支持创建Worker子线程),Worker自身的线程称为Worker子线程(或Actor线程、工作线程)。每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。
Worker拥有独立的运行环境,每个Worker线程和主线程一样拥有自己的内存空间、消息队列(MessageQueue)、事件轮询机制(EventLoop)、调用栈(CallStack)等。线程之间通过消息(Massage)进行交互。
work传输:
FFRT
FFRT是通过数据依赖的方式构建和管理异步并发任务,可以提高任务并行度、提升线程利用率、降低系统线程总数。
主要的方式是在静态编程时将应用分解成任务和数据依赖关系,程序运行时通过调度器分配任务到工作线程执行,程序员无法直接创建线程,调度器负责工作线程的创建和管理,他会根据线程执行状态去分配任务,减少空闲线程,减轻分配不均问题。他对线程的控制是通过依赖关系判断是否可执行的
数据对象可能有多个版本,每个版本有对应的消费者/生产者关系,根据这种关系可以建立依赖度 ffrt任务上下文需要使用ffrt锁
Task-Based 特性
-
任务之间可指定依赖关系,依赖关系通过Data-Driven方式表达。
-
任务可支持嵌套,即任务在执行过程中可生成新的任务下发给运行时,形成父子任务关系。
-
多任务支持互同步操作,例如等待、锁、条件变量等。
Data-Driven 特性
Data-Driven指任务之间的依赖关系通过数据依赖表达。数据对象表达抽象为数据签名,每个数据签名唯一对应一个数据对象。
依赖关系:
数据依赖抽象为任务所操作的数据对象的数据签名列表,包括输入数据依赖in_deps和输出数据依赖out_deps。数据对象的签名出现在一个任务的in_deps中时,该任务称为数据对象的消费者任务,消费者任务执行不改变其输入数据对象的内容;数据对象的签名出现在任务的out_deps中时,该任务称为数据对象的生产者任务,生产者任务执行改变其输出数据对象的内容,从而生成该数据对象的一个新的版本。
依赖关系:
Producer-Consumer 依赖
-
一个数据对象版本的生产者任务和该数据对象版本的消费者任务之间形成的依赖关系,也称为Read-after-Write依赖。
Consumer-Producer 依赖
-
一个数据对象版本的消费者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Read依赖。
Producer-Producer 依赖
-
一个数据对象版本的生产者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Write依赖。
建议
建议1: 函数化
基本思想:计算过程函数化
-
程序过程各步骤以函数封装表达,函数满足类纯函数特性。
-
无全局数据访问。
-
无内部状态保留。
-
通过ffrt_submit_base()接口以异步任务方式提交函数执行。
-
将函数访问的数据对象以及访问方式在ffrt_submit_base()接口中的in_deps/out_deps参数表达。
-
程序员通过inDeps/outDeps参数表达任务间依赖关系以保证程序执行的正确性。
做到纯函数的好处在于:1. 能够最大化挖掘并行度,2.避免DataRace和锁的问题。
在实际中,可以根据场景放松纯函数的约束,但前提是:
-
确定添加的in_deps/out_deps可确保程序正确执行。
-
通过FFRT提供的锁机制保护对全局变量的访问。
建议2: 使用FFRT提供的替代API
-
禁止在FFRT任务中使用系统线程库API创建线程,使用submit提交任务。
-
使用FFRT提供的锁,条件变量,睡眠,IO等API代替系统线程库API。
-
使用系统线程库API可能造成工作线程阻塞,引起额外性能开销。
-
建议3: Deadline机制
-
必须用于具备周期/重复执行特征的处理流程。
-
在有明确时间约束和性能关键的处理流程中使用,避免滥用。
-
在相对大颗粒度的处理流程中使用,例如具有16.6ms时间约束的帧处理流程。
建议4: 从线程模型迁移
-
创建线程替代为创建FFRT任务。
-
线程从逻辑上类似无in_deps的任务。
-
-
识别线程间的依赖关系,并将其表达在任务的依赖关系in_deps/out_deps上。
-
线程内计算过程分解为异步任务调用。
-
通过任务依赖关系和锁机制避免并发任务数据竞争问题。
已知限制
C API中初始化ffrt对象后,对象的置空与销毁由用户负责
为保证较高的性能,ffrt的C API中内部不包含对对象的销毁状态的标记,用户需要合理地进行资源的释放,重复调用各个对象的destroy操作,其结果是未定义的。
参考资料:
OpenHarmony官方文档
我想写一个即时通讯的app,长链接创建后,需要频繁的收消息发消息。
在android客户端里的实现是用到多线程的,读消息一条线程,写消息一条线程,心跳一条线程。
请教一下,在鸿蒙next中,这种场景需要使用多线程吗?如果要使用的话,推荐使用哪种呢?
即时通讯是长连接通讯,可以考虑用TaskPool任务池进行管理消息的读写的任务,他比较轻量化,不用考虑生命周期,它通过调度器和线程池对任务优先级控制,通讯应用通常需要周期性地发送心跳包以维持长连接,还有消息的发送接受都是异步用TaskPool也可以防止主线程被堵塞。可能有说的不对的地方,我学的比较浅,建议先阅读官方文档熟悉一下特性先。 参考下官方的任务池管理:https://docs.openharmony.cn/pages/v4.1/zh-cn/application-dev/reference/apis-arkts/js-apis-taskpool.md
HarmonyOS 鸿蒙中的多线程机制是其高性能和并发处理能力的重要基石。以下是对HarmonyOS中多线程的简要概述:
一、多线程的基本概念
多线程允许同一进程的多个线程并发或并行执行,共享进程资源和内存空间,提高程序的响应性和处理效率。
二、HarmonyOS的多线程支持
- Worker和线程池:HarmonyOS支持通过Worker和线程池实现多线程任务分配和管理,提升任务并发处理能力。
- 任务分发器:提供GlobalTaskDispatcher、ParallelTaskDispatcher、SerialTaskDispatcher和SpecTaskDispatcher等多种任务分发器,以满足不同场景下的任务分发需求。
- 并发处理:通过同步队列、异步任务分发等方式,实现多线程间的有效协同和通信。
三、多线程的应用场景
在HarmonyOS中,多线程广泛应用于实时数据采集和处理、复杂计算任务、用户界面更新等场景,以充分利用多核处理器和分布式系统资源,提升用户体验和系统性能。
总之,HarmonyOS的多线程机制具有灵活高效、安全可靠等特点。如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html。