Dart 线程模型、Flutter 嵌入层线程模型

3,380 阅读6分钟

Dart/Flutter 这套新组合是目前我们学习的重点和学习的新阵地,而多线程是每一个语言,每一个系统重中之重的地方,可以毫不夸张的说:代码基本都是围绕多线程来开发的,所以我们必须了解、搞懂 Dart/Flutter 中多线程的运行机制、原理

Dart 语言的线程模型和 java 有不小区别,Flutter 的多线程运行机制又和 android 又不小差别,这里需要我们搞懂的东西属实不少呢


Dart 的线程模型 Isolate

首先 Dart 是种区别于 java 的新的语言,那么自然是有区别的,在线程上 Dart 就不 是 Thead 了,而是 Isolate,意思是隔离,其实就是再说 Dart 语言线程最大的特点:内存隔离

  • Isolate 之间是代码共享,内存隔离的 举例来说:class 里有一个 var index = 1,我们启动另一个新的 Isolate 之后可以操作这个 index 并赋值,但是对于原来的 Isolate 来说,index 依然还是1。和 ThreadThreadLocal 一样,区别是 Dart 里所有的数据都是这样,线程之间数据不共享,大家这样理解就行了

  • Isolate 内部采用消息机制 这点上 DartIsolatehandle 如出一辙,其实也好理解。线程要是像一直执行下去的,无非就是 while(true) 不停的执行,不过因为 while(true) 一直执行很耗费性能和电量,所以搞出了 BlockQuere 阻塞队列的概念,没有任务就阻塞或是休眠。区别的是唤醒方式,作为进程的主线程时,BlockQuere 需要能自助唤醒,比如 handle 就是依靠 native 底层的方法来唤醒的, 其他的线程依靠内存共享来实现唤醒就 OK 了


Isolate 自带的消息机制

上面简单说了下 Dart 中如何进行多线程操作,但是 Dart 的线程模型 Isolate 自带消息队列,Flutter 中的大部分所谓的异步操作都是在 Isolate 自带消息队列里完成的,下面的部分就是围绕 Isolate 的消息队列展开的

在这里大家就能看出 Dart 和 java 在多线程上的差距了,Dart 作为后起之秀,更加了解开发者的需求,功能比 JAVA 可强多了

  • java 里提供给我们就是一个 Thread 对象,需要我们自己去实现核心 run 方法,是一次性执行,还是跑个循环队列都需要我们自己去实现
  • Isolate 则直接在线程内置提供了队列给我们,这样方便多了不是,线程在绝大多数的时候不都是采用队列去执行任务吗,要不就是用队列去控制复数的线程,比如线程池

Isolate 有2个消息队列,这点区别于 handle,虽然 handle 没搞2个消息队列,但是 handle 搞了同步屏障,一样还是2个队列的样子

Isolate 的2个队列:

  • EventQuere - 标准消息队列
  • MicroTaskQuere - 高权限消息队列

他俩的运行机制是:

优先执行MicroTaskQuere里的任务,MicroTaskQuere任务都执行完了,再去执行EventQuere任务,EventQuere每执行完一个任务,都会去检查MicroTaskQuere是否有任务了,MicroTaskQuere要是有任务了会优先执行MicroTaskQuere的任务,完事再去执行EventQuere

大家这么理解就行了


Flutter 中间层线程模型(也可以叫嵌入层)

Flutter 是一套跨语言,夸平台的 UI 库,view 渲染全部由自己完成,是目前平衡夸平台和性能的最还选择了。但是 Flutter 依然不能脱离所在平台,那么如何做才能把自己包装成一个通用接口在不同的平台上适用一套代码呢?

这是 Flutter 架构图,不同的平台之间,Framework 和 Engine 是一样的,区别的是 Embedder 中间层实现。Embedder 中间层就像寄生蔓藤和树之间的关系,Flutter 的代码都是通过 Embedder 中间层去有效的适应所属平台,承载 Flutter 自己的实现的

Embedder 中间层在 android 上的实现就是

中间层 4个 Task Runner:

  • Platform Task Runner - 负责 Flutter 中间层和 android 进程之间的通信,android app 进程所有的消息都是通过 Platform Task Runner 来接受处理,根据职责单一设计模式,很自然的 Platform Task Runner 就拥有调度整个 Flutter 执行流程的责任。所有与 Android 平台之间的交互都需要在该 task 中进行
  • UI Task Runner - 会执行 Flutter的 root Isolate代码,负责 wieght tree 的构建,解析,渲染的发起
  • GPU Task Runner - 一看 GPU 大家就应该猜到了吧,GPU Task Runner 负责把 wieght tree 渲染出来。一般来说 UI Runner 和 GPU Runner 跑在不同的线程,GPU Runner 会根据目前帧执行的进度去向 UI Runner 要求下一帧的数据,在任务繁重的时候可能会告诉UI Runner 延迟任务,这种调度机制确保 GPU Runner 不至于过载,同时也避免了 UI Runner 不必要的消耗
  • IO Task Runner - 主要功能是从图片存储(比如磁盘)中读取压缩的图片格式,对图片数据进行处理,为 GPU Runner 渲染做好准备,IO Runner 首先要读取压缩的图片二进制数据(比如PNG,JPEG),将其解压转换成GPU能够处理的格式然后将数据上传到GPU

针对不同平台,中间层 4个 Task Runner 有不同的线程配置:

  • iOSAndroid - 每一个 Engine 实例启动的时候会为 UI,GPU,IO Runner 各自创建一个新的线程,所有的 Engine 实例共享同一个 Platform Runner 和线程
  • Fuchsia` - 每一个 Engine 实例都为 UI,GPU,IO,Platform Runner 创建各自新的线程

Flutter 中渲染机制简单来说是这样的:

  • 1. Root isolate 通知 Flutter Engine 有帧需要渲染,Flutter Engine 通过 Platform Runner 通知 android 平台需要在下一个 vsync 的时候得到通知
  • 2. anroid 平台等待下一个 vsync 信号
  • 3. 当 vsync 信号发送过来时,UI Task 把 Widgets Tree 进行 Layout 计算并生成一个 Layer Tree,把这个 Layer Tree 提交给 GPU Task。这个阶段没有进行任何光栅化,仅是生成了对需要绘制内容的描述,Layer Tree 包含了用于屏幕上显示 Widgets 的各种信息,用于配置和渲染
  • 4. GPU Task 渲染完成后通过 Platform Task 通知 android 平台进行后续渲染操作,此时会切换到 Android 的 sf 进程进行 layer 图层合并,完成后会刷新数据到硬件显存中去,然后在下一个 vsync 信号发出后,硬件会从显存中读取上一帧的计算结果然后同步到显示器上

好了我们来说说卡顿问题,中间层的 4个 Task Runner 谁会的阻塞会造成卡顿 中间层 4个 Task Runner:

  • Platform Task Runner - 平台任务线程不会进行渲染,所以他的卡顿不会造成 flutter 卡顿,但是线程的阻塞可能会被系统强杀
  • UI Task Runner - 这个必然造成卡顿了
  • GPU Task Runner - 这个也会卡顿,例如加载图片的操作就不应该放在 GPU Task,而是放在 IO Task 里
  • IO Task Runner - 这个不会卡顿