前言
我是一个前端程序员,在H5前端开发中最多会遇到同步异步,并没有接触过多线程开发。最近开发鸿蒙遇到了一些并发问题,整理并总结此文章。
学习、整理的过程可谓:“越学越不会“。
例如:多线程下,如何保证单例?static能否跨线程共享。类似JAVA的双重检查锁、synchronized在鸿蒙中如何实现?如何在并发下确保原子操作?
长路漫漫,每天进步一点,学到被裁。
什么是并发?
并发是指在同一时间内,存在多个任务同时执行的情况。对于多核设备,这些任务可能同时在不同CPU上并行执行。对于单核设备,多个并发任务不会在同一时刻并行执行,但是CPU会在某个任务休眠或进行I/O操作等状态下切换任务,调度执行其他任务,提升CPU的资源利用率。
并发策略概述。
ArkTS并发策略概述
为了提升应用的响应速度与帧率,避免耗时任务对UI主线程的影响,ArkTS提供了异步并发和多线程并发两种处理策略。
解释:UI主线程
ArkTS 遵循单线程UI模型,所有UI操作必须通过主线程完成。
UI 主线程是负责管理和更新用户界面(UI)的核心线程。
主线程阻塞超过 5 秒,系统会强制弹出“应用无响应”提示。
UI主线程与浏览器处理JS相似
异步并发
异步并发概述
代码在执行到一定程度后会被暂停,以便在未来某个时间点继续执行,这种情况下,同一时间只有一段代码在执行。ArkTS通过Promise和async/await提供异步并发能力,适用于单次I/O任务的开发场景。
Promise和async/await提供异步并发能力,是标准的JS异步语法,原理可参考浏览器运行JS:
异步并发代码示例
多线程并发
多线程并发允许在同一时间段内同时执行多段代码。在UI主线程继续响应用户操作和更新UI的同时,后台线程也能执行耗时操作,从而避免应用出现卡顿。
ArkTS通过TaskPool和Worker提供多线程并发能力,适用于耗时任务并发场景。
Actor并发模型
Actor并发模型介绍
生产者生产出结果后通过序列化通信将结果发送给UI线程,UI线程消费结果后再发送新的生产任务给生产者线程。
Actor模型不同角色之间并不共享内存,生产者线程和UI线程都有自己的虚拟机实例,两个虚拟机实例之间拥有独占的内存,相互隔离。
Actor会把对象深拷贝一份到自己的虚机,成为一个“副本”,代码执行情况外界完全隔离,只对外发送结果通知。
如果希望对象在多个线程下不被深拷贝,多线程共享变量和方法,可采用Sendable。
Worker
Worker介绍
Worker拥有独立的运行环境,每个Worker线程和主线程一样拥有自己的内存空间、消息队列(MessageQueue)、事件轮询机制(EventLoop)、调用栈(CallStack)等。线程之间通过Message进行交互,如下图所示:
worker代码示例
依赖worker 和 额外全局配置
worker.ets
调用worker
TaskPool
TaskPool介绍
TaskPool在Worker之上实现了调度器和Worker线程池。
TaskPool基于Worker做了更多场景化的功能封装,例如支持任务组TaskGroup、任务优先级设置、取消任务等功能,且可以根据任务数量进行自动的扩容与缩容,还可以根据任务优先级进行任务调度。
TaskPool代码示例
依赖@Concurrent装饰器 和 taskpool方法
TaskPool和Worker对比
TaskPool与Worker两种多线程并发能力均是基于 Actor并发模型实现的。Worker主、子线程通过收发消息进行通信;TaskPool基于Worker做了更多场景化的功能封装,例如支持任务组TaskGroup、任务优先级设置、取消任务等功能,且可以根据任务数量进行自动的扩容与缩容,还可以根据任务优先级进行任务调度。
大多数多线程并发场景推荐使用TaskPool。
线程间的通讯
与宿主线程
TaskPool任务与宿主线程(UI主线程):
发
收
Worker和宿主线程(UI主线程)的消息通信:
多线程共享对象
Sendable对象为可共享的,其跨线程前后指向同一个arkTS对象:
使用Sendable过程会出现数据竞争问题,可以采用异步锁进行数据保护:
static
static为类本身的属性,无论初始化多少次,只有一个static,鸿蒙多并发场景中是否依然成立?
主UI线程/多线程开发选择
异步并发场景
适用于单次I/O任务的场景开发:例如一次网络请求操作,读取本地文件等。
多线程并发场景
- 耗时任务:大图片、视频解码;模型运算;密集的网络请求下载资源。
- 长时任务:定期采集传感器数据。
- 常驻任务:游戏中台场景(启动子线程作为游戏业务的主逻辑线程,UI线程只负责渲染);长耗时任务场景(后台长时间的模型预测任务、或者硬件测试等)。
主UI线程开发为常规arkts代码,无特殊方法和装饰器 异步并发场景通过Promise/async/await实现多线程并发通过worker方法、@Concurrent装饰器 、 taskpool方法实现。