我们知道一般常用并发机制主要包括进程、线程以及后面的协程,在Dart 语言中并发机制由Isolate实现,Isolate你可以简单的理解是一种特殊的线程。
Isolate的特点:
- Isolate之间不共享内存。
- Isolate之间只能通过消息通讯。
不共享内存,意味着你不能像线程那样通过变量共享状态,每个Isolate都有自己独立的内存,这样设计的好处就是你不用加锁,也能安全的操作自己的数据。
1.为什么需要多个Isolate实现并发处理
默认情况Dart启动一个Isolate,main函数就是他的入口,这就是为什么说Dart是单线程,默认只是启动一个Isolate, 相当于程序是单线程执行的。
我们通过异步机制,可以同时处理多个接口请求之类的异步任务,不是也有类似并发的效果吗?那么为什么还需要Isolate并发机制。
首先,一个Isolate不能利用多个CPU, 再则异步机制只是适合用于处理各种网络IO,这些网络IO不怎么消耗CPU资源,只是需要大量的等待请求响应的时间,因此我们可以利用等待的空闲时间去处理其他任务,这就是异步机制可以提高性能的原因。但是如果你有一个计算量非常大的任务,例如,你有大量的数据要处理,需要对视频进行格式化处理 ,这个时候这些CPU密集型计算会阻塞你的线程,其他任务都执行不了了。
针对比较消耗CPU的任务,最好创建一个新的 Isolate 去处理,避免阻塞主 Isolate (也就是主线程),这样可以利用设备的多核特性。
2. Isolate 基本用法
通过例子了解Isolate基本用法。
// 导入isolate包
import 'dart:isolate';
void main() {
// 通过Isolate.spawn静态函数,创建一个新的Isolate
// spawn是一个泛型函数,接受一个泛型参数,表示Isolate入口函数接受的参数类型
// 这里spawn的泛型参数是String,subTask是入口函数
// 第二个参数跟泛型参数类型一致,表示传递给入口函数的参数,这里传入的是字符串。
Isolate.spawn<String>(subTask, "Task1 parameter");
// main函数结束标记
print("main func end.");
}
// Isolate入口函数定义,接受一个String参数
// 入口函数的参数类型由上面的spawn的泛型参数决定。
void subTask(String msg) {
print("subTask recv: $msg");
}
输出结果:
main func end.
subTask recv: Task1 parameter
通过输出,我们发现先打印了 main func end. 然后,执行新建Isolate的入口函数。 如果我们想让代码执行顺序,跟我们书写顺序一致的话,可以使用await关键词等待Isolate执行结束
例子改写为:
// 导入isolate包
import 'dart:isolate';
// 使用async关键词将main函数标记为一个异步函数,这样才能使用await关键词
void main() async {
// 使用await关键词等待任务执行完成
await Isolate.spawn<String>(subTask, "Task1 parameter");
// main函数结束标记
print("main func end.");
}
// Isolate入口函数定义,接受一个String参数
void subTask(String msg) {
print("subTask recv: $msg");
}
输出:
subTask recv: Task1 parameter
main func end.
这个时候输出顺序跟我们代码书写顺序一致了。
3.Isolate 消息通讯
多个Isolate 之间只能通过消息通讯,例如,我们如何获取一个Isolate的计算结果。
主要通过ReceivePort和SendPort两个类处理消息通讯。
ReceivePort 负责接收 SendPort 发送的消息, SendPort 和 ReceivePort 是捆绑关系, SendPort 是由 ReceivePort 创建的。
例子:
// 导入isolate包
import 'dart:isolate';
void main() async {
// 创建一个ReceivePort用于接收消息
var recv = ReceivePort();
// 创建一个Isolate,泛型参数为SendPort,入口函数为subTask
// subTask入口函数的参数为SendPort类型,因此spawn第二个参数,传入recv的sendPort对象。
Isolate.spawn<SendPort>(subTask, recv.sendPort);
// 使用await等待recv的第一条消息
var result = await recv.first;
print("recv: $result");
}
// Isolate入口函数定义,接收一个SendPort对象作为参数
void subTask(SendPort port) {
// 使用SendPort发送一条字符串消息
port.send("subTask Result.");
}
输出:
recv: subTask Result.