LoginSignup
16
6

More than 3 years have passed since last update.

dart isolate を理解したい

Last updated at Posted at 2020-04-26

dart:isolate とは

dart:isolate は、dart で 並行処理 を行うためのライブラリです。
isolate は、分離させる という意味ですね。

isolate は、スレッドとは似て非なるものです。メモリを共有せず、各 isolate はポート介してメッセージを通信できます。

使用法

この記事では説明の都合上、分離元を親分離先を子 と表現します。

子から親へ通信する

このパターンが一番簡単なので、これを最初に紹介します。

childToParent.dart

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 へとメッセージが渡されます。

これが最も基本的な例です。これを拡張していくことで、複雑なことができるようになります。

親から子へ通信する

「子から親」よりは少し複雑です。

parentToChild.dart
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 を用いることで各プロセスを分離させ、あたかも同時に実行されているように感じます。

16
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
6