Dart / Flutterでは、Mutex / Lockのような排他制御は考えなくてよさそう(たぶんほとんどの場合)

そもそもDartには他の言語で言うところのマルチスレッドという概念はない。Isolateはスレッドというよりプロセスに近いものであろう。Isolate間は同じメモリ領域を参照することはできないから。

asyncメソッドをawaitすることなく連続して呼び出すと、それぞれ別スレッドで実行するように思うかもしれないが、実際はそんなことはない。それらのメソッドはキューに詰まれ、同じスレッドで順番に実行されるだけのようだ。

以下のサンプルコードでそれを確認できた。

Future<void> _asyncMethod(String key, Duration duration) async {
  print("Start $key");
  for (int i = 0; i < 10; i++) {
    print("Loop $key");
  }
  print("End $key");
}

_asyncMethod("1");
_asyncMethod("2");

実行結果は以下のようになる。

Start 1
Loop 1
Loop 1
Loop 1
Loop 1
Loop 1
Loop 1
Loop 1
Loop 1
Loop 1
Loop 1
End 1
Start 2
Loop 2
Loop 2
Loop 2
Loop 2
Loop 2
Loop 2
Loop 2
Loop 2
Loop 2
Loop 2
End 2

もしマルチスレッドで実行されているなら以下のようになりそうである。

Start 1
Start 2
Loop 1
Loop 2
Loop 1
Loop 2
Loop 1
Loop 2
.
.
.
End 1
End 2

実際、こんなにきれいにはならないと思うが、ループ回数が10回ではなく1万回などに増やすと、Loop1とLoop2は入り混じって出力されるだろう。

Future.delayのawaitを挟むと以下のようになる。

Future<void> _asyncMethod(String key, Duration duration) async {
  print("Start $key");
  await Future.delayed(duration);
  print("End $key");
}

_asyncMethod("1", Duration(seconds: 3));
_asyncMethod("2", Duration(seconds: 1));

出力結果。

Start 1
Start 2
End 2
End 1

最初のメソッドが中断されて、次のメソッドが開始されるが、マルチスレッドで動いているわけではない。最初のメソッドがFuture.delayedをawaitしたから、次のメソッドに順番が回ってきただけのことである。

これらのことから、以下のような処理をしても、counter変数を同時にRead/Writeするようなことは起きないはずである。

int counter = 0;

Future<void> _asyncMethod() async {
  counter += 1;
  print(counter);
}

for (int i = 0; i < 100; i++) {
  _asyncMethod("$i");
}

出力するとcounterの値は常に順番で表示される。

1
2
3
4
5
6
7
.
.
.
99
100