Open3
「Isolate」を勉強するスレット
- Dartはシングルスレッド/イベントループ
-
Main isolate
: 通常の処理はすべてこのシングルスレッド上で実行する - キューに溜まったイベントが順に実行する
-
-
Futures
&Streams
APIなどは並行処理できるが、並列処理できない- シングルスレッド(Main Isolate)上での並行処理
- ネットワーク通信のレスポンス待ちなどCPU負荷が低い処理は、並行処理(Futures/Streams)で良い
- CPU負荷の高い処理は、
Isolate
を起動する-
Isolate
を直接扱うとコードが汚い。そのため、compute
関数(Isolateの処理をラップ)で済む場合、優先的に使う - Main以外のIsolateを起動し、複数のIsolateで処理する = Dart版のマルチスレッドを用いた並列処理
- スイッチングコスト(Isolate間の通信時間)が発生する。
-
- Isolateの特徴
- それぞれがイベントループを持つ
- Isolate同士でメモリーを共有することはできず、データはメッセージよって交換する
- プラットフォーム毎に実態は異なる
- Dartの孤立した実行コンテキスト
- コードはIsolateで実行され、同じIsolateならクラスや値にアクセス可
- 別Isolateの情報にアクセスする場合、(ReceivePort or sendPort で)ポートでデータ送信したらアクセス可
- Isolateオブジェクトを送れないけど、制御ポートや機能は送れる
- 送信されたデータをもとに新しいIsolateオブジェクトを作成可能
-
Main Isolate
とIsolate A
は異なる。そのため、Main Isolate
はIsolate A
をコントロールできる - Isolateの作成(spwan)に成功すると、新しいIsolateオブジェクト(制御ポートや能力)を受け取る(
Isolate.Isolate
コンストラクタで能力を制限可能) - Isolaeは個別のイベントループ
- Isolate間でイベントループの制御、一時停止、エラー検知などが可能
-
Isolate
を作成すれば、マルチプロセッサコアでコードを並列実行可能 - 複数の
Isolate
はDart Natie platform
にのみ対応。そのため、Web環境はWeb Workers APIを使う - マルチコアCPUを利用するためには、開発者は共有スレッドメモリを活用しなければならない。しかし、共有状態の同時実行はコードが複雑かつエラーが発生しやすい。
- Dartは
Isolate
内部で実行する(= スレッド内で直接実行されない)
MainIsolate
- 大抵の場合、Isolateに関して考えなくていい
- 多くの場合、MainIsolateで事足りる
- async / awaitで非同期処理すればOK
- 正常に動作するアプリの定義
- 高速起動及び、イベントループを実行
- 必要に応じて非同期操作を行い、キューに入れられた各イベントを迅速に応答
Isolateのライフサイクル
- Isolateは実行系(main関数など)のDartコードから始まる
- イベントの終了と同時にIsolateも終了する(= 削除される?)
- イベントのリスナーが登録されている場合、Isolateはリスナーが終了するまで残る
- 例 : Isolateのライフサイクル
-
1.Run some code
でIsolateが始まる -
2.Optionally, ....
でイベントのリスナーが登録される - リスナーが処理されるまでIsolateは居続ける
- リスナーの処理終了後、Isolateは終了する
-
イベント処理
- キュー(queue)は先着順(先入れ、先出し)で基本的に処理される
- エンキュー(enqueue) : データを入れること。データは末尾に追加され、行列の長さは1増加
- デキュー(dequeue) : データを取り出すこと。データの先頭から取り出し、行列の長さは1減少
- イベント処理はmain()終了後のMainIsolateで実行する
- 同期操作に時間がかかりすぎると、レスポンスしない可能性が生まれる
- アプリがフリーズしているよに見える
- とぎれとぎれのアニメーションになる(= 崩れたアニメーション)
- UIがフリーズする
タスクの分離(Isolateの分離)
- 大規模計算でUIが応答しない(バックグラウンドワーカーの)場合、別のIsolateにオフロード(= Isolateの分離)するのを検討する
- 例 : 計算処理を別Isolateで実行する
-
Main isolate
でmainが実行 - 計算処理用に
Woker isolate
(計算用のIsolate)を作成し、処理を移譲する -
Worker isolate
で計算処理し、結果をMain isolate
に返す。その後isolateのイベントループが終了する -
Main isolate
はWorker isolate
から結果を受け取り、イベントループを終了する
-
- Isolateは1つのオブジェクトを配達(= 送信)することができる
- 配達したオブジェクトが推移的に到達できるものでも可(= Isolate本体を配達できないが、配達したオブジェクトで別のIsolateは作成可)
- オブジェクト全てを送信できない
- 推移的に到達可能なオブジェクトが送信不能の場合、送信は失敗する
- 例 : 送信できないケース
- List<Object>の要素内に送信不可能なオブジェクトが1つでも含まれている場合、失敗する
- Socketは送信できないため、失敗する
- 例 : 送信できないケース
- Isolateは独自のメモリがあり、Isolate間で共有しない。
- Isolateを分離し、重たい処理(block)を実行すると他Isolateに悪影響を与えない
コード例
- Flutterの場合、compute()を使うと簡単にタスクを分離できる
MainIsolateで実行される
void main() async {
// Read some data.
final jsonData = await _parseInBackground();
// Use that data
print('number of JSON keys = ${jsonData.length}');
}
// Spawns an isolate and waits for the first message
Future<Map<String, dynamic>> _parseInBackground() async {
final p = ReceivePort();
await Isolate.spawn(_readAndParseJson, p.sendPort);
return await p.first;
}
WorkerIsolateで実行する
Future _readAndParseJson(SendPort p) async {
final fileData = await File(filename).readAsString();
final jsonData = jsonDecode(fileData);
Isolate.exit(p, jsonData);
}
MainIsolate&(MainIsolateが生成した)WorkerIsolateでタスク分離の実装例。
- WorkerIsolateは関数を実行し、終了する。
- MainIsolateが終了する時にメッセージを送信する。
-
_parseInBackground()
は、WorkerIsolate
を生成(作成&開始)し、結果を返すコード-
WorkerIsolate
を作成する前にReceivePort classを作成する。これにより、WorkerIsolate
はMainIsolate
に結果を返すことができる。 -
spawn<T> methodを呼び出す。このメソッドにより、
WorkerIsolate
は作成&開始する。第1引数の_readAndParseJson
はWorkerIsolate
が実行する関数を指定し、第2引数のp.sendPort
はWorkerIsolate
がMainIsolate
に送信するRecivePort
を指定する。 -
WorkerIsolate
が生成されるとMainIsolate
は結果を待つ。ReceivePort class
はStream class
をラップしているため、firstdプロパティでWorkerIsolate
が送信される単一のメッセージを取得する。
-
-
exit methodで
WorkerIsolate
を同期的に終了し、データを送信する。