14.1 同期処理

一つの処理が終わるまで、次の処理へ進みません。

APIへ接続
応答を待つ
結果を処理

待機中に他の作業ができないため、I/O処理では効率が悪い場合があります。


14.2 非同期処理

処理の完了を待っている間に、別の処理を進めます。

const response = await fetch(url);
response = await client.get(url)
let response = client.get(url).send().await?;

asyncは、関数が即座に最終結果を返すのではなく、将来完了する処理を返すことを表します。


14.3 並行と並列

この二つは異なります。

並行

複数の処理を進行中の状態にすることです。

処理Aを少し進める
処理Bを少し進める
処理Aへ戻る

並列

複数の処理を物理的に同時実行することです。

CPUコア1で処理A
CPUコア2で処理B

非同期処理は並行性を実現しますが、必ずしも並列ではありません。


14.4 スレッド、タスク、コルーチン

概念 概要
OSスレッド OSが管理する実行単位
軽量スレッド 言語ランタイムが管理する軽量な実行単位
タスク 非同期処理の論理的な単位
コルーチン 中断・再開できる関数
Future・Promise 将来得られる結果

Goのgoroutine、Kotlinのcoroutine、JavaScriptのPromise、RustのFutureなどは、同じ問題へ異なる抽象化を提供しています。


14.5 共有状態とメッセージパッシング

並行処理では、複数の処理が同じ状態を変更すると競合が起きます。

タスクAが残高を読む
タスクBが同じ残高を読む
両方が更新する
一方の更新が失われる

対処方法は大きく二つあります。

共有状態を同期する

Mutex
Lock
Atomic
Semaphore

状態を共有せずメッセージを送る

Channel
Actor
Message Queue

Goではチャネル、Erlang系ではActorモデル、Rustでもチャネルなどを利用できます。


14.6 構造化並行性

開始した非同期処理の寿命を、親処理のスコープへ結び付ける考え方です。

親タスク
├─ 子タスクA
└─ 子タスクB

親処理が終了するとき、子処理も完了・キャンセルされることを保証します。

目的は次です。

処理の取り残しを防ぐ
キャンセルを伝播させる
エラーをまとめて扱う
タスクの寿命を明確にする