Dart语言中的Isolate

Posted Androider_Zxg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dart语言中的Isolate相关的知识,希望对你有一定的参考价值。

概述

Dart本身是Google推出的web开发语言,所以Dart语言在设计上很多方面都借鉴了JS。例如JS的面向对象、单线程、事件循环等。同时也做了许多优化,比如javascript低效的解释执行,而Dart可以在运行前直接编译为机器码,提高了执行效率,这个过程叫做AOT(Ahead of time)。还有JavaScript的单线程模型,只能依赖JS解释引擎的异步任务执行机制,开发者没有办法自己启动新的线程去执行耗时代码,但是Dart却提供了这个能力 — Isolate。Isolate是一种特殊的线程,多个Isolate之间内存独立不共享,而我们概念中的线程是共享其所在进程的内存的。个人理解是底层提供线程,应用层不能直接创建传统意义上的线程。只能使用底层提供的封装的Isolate,来实现在非主线程中进行耗时操作。

Isolate是什么

Isolate直译过来是“隔离”的意思,也就是各个Isolate是不干扰的,互相隔离的。

import 'dart:async';
import 'dart:isolate';

int i = 0;
IntObject intObject = IntObject();

void main() async {
  //Isolate的消息接收端口
  final receive = ReceivePort();
  //为消息接收端口添加监听
  receive.listen((data) {
    print(
        "Main: Receive data =  $data, i = $i, intObject = ${intObject.get()}");
  });
  // 创建一个Isolate实例,1.指定入口函数 2.指定消息通信发送端口
  Isolate isolate = await Isolate.spawn(isolateEntryFunction, receive.sendPort);
  print(DateTime.now().toString() + " start...");
}

// isolate入口函数,该函数必须是静态的或顶级函数,不能是匿名内部函数。
void isolateEntryFunction(SendPort sendPort) {
  int counter = 0;
  Timer.periodic(const Duration(seconds: 3), (_) {
    counter++;
    //在单独的Isolate实例中修改i的值
    i++;
    intObject.increase();

    String sendMsg = "Notification data: $counter";
    print(DateTime.now().toString() +
        " Isolate: counter = $counter, i = $i, intObject = ${intObject.get()}");
    sendPort.send(sendMsg);
  });
}

class IntObject {
  int _i = 0;
  void increase() {
    _i++;
  }
  int get() {
    return _i;
  }
}

结果:

2019-10-31 11:54:13.525943 start...
2019-10-31 11:54:16.537207 Isolate: counter = 1, i = 1, intObject = 1
Main: Receive data =  Notification data: 1, i = 0, intObject = 0
2019-10-31 11:54:19.531389 Isolate: counter = 2, i = 2, intObject = 2
Main: Receive data =  Notification data: 2, i = 0, intObject = 0
2019-10-31 11:54:22.527753 Isolate: counter = 3, i = 3, intObject = 3
Main: Receive data =  Notification data: 3, i = 0, intObject = 0
2019-10-31 11:54:25.532155 Isolate: counter = 4, i = 4, intObject = 4
Main: Receive data =  Notification data: 4, i = 0, intObject = 0

我们可以得到如下结论:

1、在新建的Isolate中修改i和intObject变量的值后,在main()函数所在Isolate中不能获取到,所以Isolate在内存上是隔离的。

2、Isolate虽然是隔离的,但提供了通信机制,叫做端口。

Isolate底层逻辑

虽然在内存表现上,Isolate内存隔离性像是进程的特点。但是从实现上不可能把Isolate作为一个进程,因为进程太重了,每新建一个进程,内核系统都会为新进程创建独立的虚拟内存,保存进程相关的数据结构,并且进程切换效率比较低。所以从可行性上来说Isolate的本质应该是一个线程。也就是说Isolate是通过线程实现的。我们使用多个Isolate也就是使用多个线程,只不过与传统线程不同的是,Isolate之间内存不共享,但可以通过通信机制互通。

从源码中验证,Isolate底层是线程。详情参考链接

Isolate如何实现内存隔离

通过跟踪Isolate的创建过程中,可以看到在新建一个Isolate实例后,会初始化一个Heap对象,并设置给Isolate。这个Heap应该就是当前Isolate独享的堆内存管理器。

// vm/isolate.cc

Isolate* Isolate::InitIsolate(const char* name_prefix,
                              IsolateGroup* isolate_group,
                              const Dart_IsolateFlags& api_flags,
                              bool is_vm_isolate) {
  Isolate* result = new Isolate(isolate_group, api_flags);
  ...
  
    Heap::Init(result,
             is_vm_isolate
                 ? 0  // New gen size 0; VM isolate should only allocate in old.
                 : FLAG_new_gen_semi_max_size * MBInWords,
             (is_service_or_kernel_isolate ? kDefaultMaxOldGenHeapSize
                                           : FLAG_old_gen_heap_size) *
                 MBInWords);
                 
    ... 
     
}

从代码中可以看Isolate的堆内存也被区分为新生代和老年代两块区域,Dart虚拟机针对不同的区域执行不同的垃圾回收策略:
新生代采用复制清除算法,针对频繁创建销毁的页面控件对象,可以从内存层面实现快速分配和回收。
老年代采用标记清除和标记整理两种算法,来适应不同的内存回收场景,尽量保证UI的流畅性。
这里也就解释了在文章开头提到的Dart中简单内存分配模型,和高效垃圾的回收机制。

Isolate通信

创建Isolate时需要指定一个接收端口(ReceivePort)的发送端口(SendPort),调用者可以通过这个发送端口发送数据到其他的Isolate中ReceivePort的listen中,这种机制被称为消息传递(message passing)

既然是内存隔离的,那么在调用者所在Isolate发送的消息数据是怎么传递到接收者所在的Isolate中的呢?也就是说Isolate通信的底层逻辑是什么呢?

答案是map_变量,map_是一个Hash列表。是在Dart虚拟启动时初始化的,所以map_变量是存在于Dart虚拟机所属内存的,而这块内是各个Isolate共享的

为什么将Isolate设计成隔离的

1、首先说目前由移动端页面(包含androidios、Web)构建的特性—树形结构构建布局、布局解析抽象、绘制、渲染,这一系列的复杂步骤导致必须在同一个线程完成。**因为多线程操作页面UI元素会有并发的问题,有并发就必须要加锁,加锁就会降低执行效率。所以强制在同一线程中操作UI是最好的选择。**况且在Flutter中,开发者面对的只有一个主Isolate,在Isolate中可以通过事件队列来实现异步(网络请求、文件IO)等。所以不需要再使用其他线程完成异步。

2、每当有页面交互时,必定会引起布局变化而重新绘制,这个过程会有频繁的大量的UI控件的创建和销毁,这就涉及到了耗时内存分配和回收。而这些较短生命周期的对象是存放在堆内存的新生代的,当虚拟机回收新生代内存时是要stop the world的,在Android或iOS中,各个线程共用一块堆内存,当非UI线程频繁申请、释放内存时也会触发垃圾回收,所以会间接影响UI线程的运行。

Dart为了解决这个问题,就每个Isolate(看做线程)分配各自的一块堆内存,并且独自管理内。这样的策略使得内存的分配和回收变得简单高效,并且不受其他Isolate的影响。

总结

1、Dart中向应用层提供了线程的封装——Isolate。应用层是不能创建线程的,只能使用Isolate
2、Isolate与传统的线程不同的是,内存隔离
3、Isolate设计成隔离的,是出去移动端页面UI构建特性考虑。第一点,UI绘制必须在同一线程内完成
所以强制同一线程是最好的选择。第二点,传统的线程内存共享,其他线程频繁的申请释放内存会触发垃圾回收,间接影响UI线程运行。

参考链接:https://blog.csdn.net/joye123/article/details/102913497

以上是关于Dart语言中的Isolate的主要内容,如果未能解决你的问题,请参考以下文章

Dart语言中的Isolate

Dart语言中的Isolate

Dart 语言异步编程之Isolate

聊一聊Flutter Engine线程管理与Dart Isolate机制

异步编程之 Isolate

异步编程之 Isolate