dart:isolate とは
dart:isolate
は、dart で 並行処理 を行うためのライブラリです。
isolate は、分離させる という意味ですね。
isolate は、スレッドとは似て非なるものです。メモリを共有せず、各 isolate はポート介してメッセージを通信できます。
使用法
この記事では説明の都合上、分離元を親
、分離先を子
と表現します。
子から親へ通信する
このパターンが一番簡単なので、これを最初に紹介します。
import "dart:isolate";
void main() async {
final receivePort = ReceivePort();
final sendPort = receivePort.sendPort;
receivePort.listen((message) {
print(message);
receivePort.close();
});
await Isolate.spawn(_child, sendPort);
}
void _child(SendPort parentSendPort) {
parentSendPort.send('i am child.');
}
これを実行すると、
$ dart childToParent.dart
i am child.
このようになります。それぞれ解説していきます。
ReceivePort
これは、例えるなら郵便受けです。他の isolate からのメッセージが、ここに入ります。
final receivePort = ReceivePort();
ここで、郵便受けを設置しました。親に届いたメッセージが入る郵便受けです。
SendPort
郵便受けを設置しても、メッセージは届きません。なぜなら、郵便受けの住所がわからないからです。この SendPort は、郵便受けの住所です。
final sendPort = receivePort.sendPort;
こうすることで、郵便受けの住所を獲得できます。
つまり、この SendPort(住所) 宛にメッセージを送ることで、ReceivePort(郵便受け) にメッセージが届きます。
これが、isolate の基本的な考え方です。
ReceivePort.listen((message) {})
これは、郵便受けにメッセージが届いたらどうするか? ということです。
上の例では、メッセージを print した後、receivePort.close();
で郵便受けを閉じます。
よって、今回の ReceivePort は、一回メッセージを受け取ると、それ移行のメッセージは破棄します。
Isolate.spawn()
これは、Isolate(子) を生成します。第1引数は、Isolate した先、つまり子で実行する関数名です。第2引数は、親の住所である SendPort を渡します。この spawn の第2引数は、子の第1引数に渡ります。
今回の子は、
parentSendPort.send('i am child.');
という処理をします。
つまり、親の SendPort に対して、「俺は子供だぜ」と送りつけます。そうすると、先程の ReceivePort.listen
へとメッセージが渡されます。
これが最も基本的な例です。これを拡張していくことで、複雑なことができるようになります。
親から子へ通信する
「子から親」よりは少し複雑です。
import "dart:isolate";
void main() async {
final receivePort = ReceivePort();
final sendPort = receivePort.sendPort;
await Isolate.spawn(_child, sendPort);
receivePort.listen((message) {
final childSendPort = message as SendPort;
childSendPort.send('i am parent');
receivePort.close();
});
}
void _child(SendPort parentSendPort) {
final receivePort = ReceivePort();
final sendPort = receivePort.sendPort;
parentSendPort.send(sendPort);
receivePort.listen((message) {
print(message);
receivePort.close();
});
}
親から子への通信のポイントは、子にも郵便受け(SendPort)を用意して、その住所(SendPort)を親に伝えること
です。
ですので、先程の「子から親への通信」ができていないと、「親から子への通信」は実現できません。
子の SendPort 親に伝える部分が、_child関数内の
parentSendPort.send(sendPort);
です。
親の住所に対して、子の住所を送りつけるんですね。
親側は以前同様、receivePort.listen()
で受け取り、受け取った子の住所に対して
childSendPort.send('i am parent');
でメッセージを送ります。
子も同様にlisten
で親からのメッセージを受け取り、print します。
実行結果は、
$ dart parentToChild.dart
i am parent
となります。
並行処理を実感する
並行処理を実感できるサンプルを以下に示します。
import "dart:isolate";
void main() async {
final receivePort = ReceivePort();
final sendPort = receivePort.sendPort;
receivePort.listen((message) {
print(message);
});
await Isolate.spawn(_child1, sendPort);
await Isolate.spawn(_child2, sendPort);
}
void _child1(SendPort initialReplyTo) async {
while(true) {
initialReplyTo.send("i am child1");
await new Future.delayed(new Duration(seconds: 1));
}
}
void _child2(SendPort initialReplyTo) async {
while(true) {
initialReplyTo.send("i am child2");
await new Future.delayed(new Duration(seconds: 1));
}
}
_child1、_child2は、どちらも 親にメッセージを送信して1秒止まる while ループです。親は、2つの子からのメッセージを print するだけです。
これの実行結果は以下のようになります。
i am child1
i am child2
// 1秒止まる
i am child1
i am child2
// 1秒止まる
i am child1
i am child2
// 以下、同様
^C
このように、isolate を用いることで各プロセスを分離させ、あたかも同時に実行されているように感じます。