簡介
之前介紹了很多dart中的非同步程式設計技巧,不知道大家有沒有發現一個問題,如果是在java的非同步程式設計中,肯定會提到鎖和併發機制,但是對於dart來說,好像從來沒有聽到多執行緒和併發的問題,這是為什麼呢?
今天,給大家講解一下dart中的隔離機制,大家就明白了。
dart中的隔離機制
dart是一個單執行緒的語言,但是作為一個單執行緒的語言,dart卻支援Future,Stream等非同步特性。這一切都是隔離機制和事件迴圈帶來的結果。
首先看一下dart中的隔離機制。
所謂隔離指的是dart執行的一個特定的空間,這個空間擁有單獨的記憶體和單執行緒的事件迴圈。
如下圖所示:
在java或者c++等其他語言中,多個執行緒是共享記憶體空間的,雖然帶來了併發和資料溝通的方便途徑,但是同時也造成了併發程式設計的困難。
因為我們需要考慮多執行緒之間資料的同步,於是額外多出了很多鎖的機制,詳細瞭解或者用過的人應該都會很煩惱。
多執行緒最大的缺陷就是要求程式設計師的羅輯思維和程式設計技巧足夠優秀,這樣才能夠設計出完美執行的多執行緒程式。
但是在dart中,這些都不是什麼問題。dart中所有的執行緒都擁有自己的執行空間,這個執行緒的工作就是執行事件迴圈。
那麼問題來了,主執行緒在處理事件迴圈,但是如果遇到了一個非常耗時的操作,該怎麼辦呢? 如果直接在主執行緒中執行,則可能會導致主執行緒的阻塞。
dart也充分考慮到了這個問題,所以dart提供了一個Isolate的類來對隔離進行管理。
因為dart程式本身就在一個Isolate中執行,所以如果在dart中定義一個Isolate,那麼這個Isolate通常表示的是另外一個,需要和當前Isolate進行通訊的Isolate。
生成一個Isolate
那麼如何在當前的dart程式中生成一個Isolate呢?
Isolate提供了三種生成方法。
一個非常常用的是Isolate的工廠方法spawn:
external static Future<Isolate> spawn<T>(
void entryPoint(T message), T message,
{bool paused = false,
bool errorsAreFatal = true,
SendPort? onExit,
SendPort? onError,
@Since("2.3") String? debugName});
spawn會建立一個新的Isolate,呼叫它需要傳入幾個引數:
entryPoint表示的是生成新Isolate的時候需要呼叫的函式。entryPoint接受一個message引數。通常來說message是一個SendPort物件,用於兩個Isolate之間的溝通。
paused表示新生成的Isolate是否處於暫停狀態,他相當於:
isolate.pause(isolate.pauseCapability)
如果後續需要取消暫停狀態,則可以呼叫:
isolate.resume(isolate.pauseCapability)
errorsAreFatal 對應的是setErrorsFatal方法。
onExit對應的是addOnExitListener, onError對應的是addErrorListener。
debugName表示的是Isolate在除錯的時候展示的名字。
如果spawn出錯,則會丟擲IsolateSpawnException異常:
class IsolateSpawnException implements Exception {
/// Error message reported by the spawn operation.
final String message;
@pragma("vm:entry-point")
IsolateSpawnException(this.message);
String toString() => "IsolateSpawnException: $message";
}
spawn方法生成的是和當前程式碼一樣的Isolate。如果想要使用不同的程式碼來生成,則可以使用spawnUri,通過傳入對應的Uri地址,從而生成不一樣的code。
external static Future<Isolate> spawnUri(
Uri uri,
List<String> args,
var message,
{bool paused = false,
SendPort? onExit,
SendPort? onError,
bool errorsAreFatal = true,
bool? checked,
Map<String, String>? environment,
@Deprecated('The packages/ dir is not supported in Dart 2')
Uri? packageRoot,
Uri? packageConfig,
bool automaticPackageResolution = false,
@Since("2.3")
String? debugName});
還有一種方式,就是使用Isolate的建構函式:
Isolate(this.controlPort, {this.pauseCapability, this.terminateCapability});
它有三個引數,第一個引數是controlPort,代表另外一個Isolate的控制權,後面兩個capabilities是原isolate的子集,表示是否有pause或者terminate的許可權。
一般用法如下:
Isolate isolate = findSomeIsolate();
Isolate restrictedIsolate = Isolate(isolate.controlPort);
untrustedCode(restrictedIsolate);
Isolate之間的互動
所有的dart程式碼都是執行在Isolate中的,然後程式碼只能夠訪問同一個isolate內的class和value。那麼多個isolate之間通訊,可以ReceivePort和SendPort來實現。
先看下SendPort,SendPort是Capability的一種:
abstract class SendPort implements Capability
SendPort用於向ReceivePort傳送message, message可以有很多型別,包括:
Null,bool,int,double,String,List,Map,TransferableTypedData,SendPort和Capability。
注意,send動作是立馬完成的。
事實上,SendPort是由ReceivePort來建立的。一個ReceivePort可以接收多個SendPort。
ReceivePort是Stream的一種:
abstract class ReceivePort implements Stream<dynamic>
作為Stream,它提供了一個listen用來處理接收到的訊息:
StreamSubscription<dynamic> listen(void onData(var message)?,
{Function? onError, void onDone()?, bool? cancelOnError});
一個例子
講了那麼多原理,有的同學可能會問了,那麼到底怎麼用呢?
例子來了:
import 'dart:isolate';
var isolate;
void entryPoint(SendPort sendPort) {
int counter = 0;
sendPort.send("counter:$counter");
}
void main() async{
final receiver = ReceivePort();
receiver.listen((message) {
print( "接收到訊息 $message");
});
isolate = await Isolate.spawn(entryPoint, receiver.sendPort);
}
在主執行緒中,我們建立了一個ReceivePort,然後呼叫了它的listen方法來監聽sendPort發過來的訊息。
然後spawn出一個新的Isolate,這個Isolate會在初始化之後,呼叫entryPoint方法。
在這個entryPoint方法中又使用sendPort向ReceivePort傳送訊息。
最終執行,列印:
接收到訊息 counter:0
總結
以上就是dart中的隔離機制和Isolate的使用。
本文已收錄於 http://www.flydean.com/25-dart-isolates/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!