dart isolate を理解したい

# dart:isolate とは

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

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


# 使用法

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

## 子から親へ通信する

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

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

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

## 親から子へ通信する

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

```dart: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
```

となります。

# 並行処理を実感する

並行処理を実感できるサンプルを以下に示します。

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