TL;DR
- 非同期処理とは、複数のタスクがあるときに、1 つのタスクの終了を待たずに別の仕事を進めることであり、実現方法とは独立した概念である。
- スレッドと async/await の違いは、スケジューリングの方式、スケジュールする主体、スタックの有無にある。多くのアプリケーションはスレッドで十分である。
内容
非同期処理と Rust での実現方法
非同期処理はそれを実現する方法とは独立した概念である。複数のタスクがあるときに、1 つのタスクの終了を待たずに別の仕事を実行することを指す。
Rust で非同期処理を実現する方法は、次の 4 つである。
- プロセスを使う
- スレッドを使う
- async/await を使う
- 非同期 I/O のライブラリとコールバック関数を組み合わせる
スレッドと async/await の違い
スレッドを使っても async/await を使っても、できることに大きな差はない。スレッドは OS が時分割でスケジュールするので、コア数とは関係なく同時並行に動作する。スレッドの実行が I/O でブロックすると、OS が別のスレッドにコンテキストスイッチするため、CPU サイクルが無駄に消費されることはない。
スレッドと async/await の本質的な違いは次の 3 つである。
1 つ目はスケジューリングの方式である。スレッドはプリエンプティブにスケジュールされるので、それを考慮した排他制御が必要になる。async/await はノンプリエンプティブなので、1 つのスレッドで実行されると仮定できるなら排他制御はいらない。ただし、スレッドプールで実行する場合は、他のスレッドで別のタスクが実行されることがあるため、排他制御が必要になる。
2 つ目はスケジュールする主体である。スレッドは OS がスケジューリングするので、切り替えのコストが高い。async/await は OS が介在せず、ユーザ空間の中で切り替えるため、スレッドよりコストが低い。
3 つ目はスタックの有無である。スレッドはスタックを使って動作するので、極端に多くのスレッドを作るとメモリを大量に消費する。async/await のタスクにはスタックが割り当てられない。個々のタスクは、エグゼキュータが動かすスレッドのスタックを使って実行されるが、タスク自体はスタックを消費しない。そのため、大量のタスクを起動してもそれほどメモリを使わない。
async/await が必要になる場面
多くのアプリケーションではスレッドで十分である。async/await が必要になるのは、次のような場合である。
- ネットワークサーバのように、ファイル I/O やネットワーク I/O などの多数の I/O を並行して管理する必要がある場合
- メモリなどのリソースが限られている場合
キーワード
自分の理解
プロセスやスレッドについてはある程度知っているので、さくさく読み進めた。章の前半では、ユーザ空間、カーネルレベルスレッドとユーザレベルスレッド、グリーンスレッド(例: Tokio)、メモリオーダリングといった用語が出てくる。