並行処理と非同期
並行処理とは
サーバーの処理時間の多くは、実は「待ち時間」です。データベースの応答、外部APIの返事、ファイルの読み書き — その間 CPU は何もしていません。この待ち時間に別の仕事を進めるのが並行処理です。1人の店員が、麺をゆでている間にレジを打つイメージです。
並行と並列の違い
| 並行(Concurrency) | 並列(Parallelism) | |
|---|---|---|
| 意味 | 複数の処理を切り替えながら進める | 複数の処理を同時に実行する |
| 必要なもの | 切り替えの仕組み | 複数のCPUコア |
| 例え | 1人の店員が複数の作業を回す | 店員が2人いて同時に働く |
CPUコアが1つでも並行処理はできますが、並列実行はできません。待ち時間の性質でも手段は変わり、通信やディスクの待ちが中心のI/Oバウンドには非同期I/Oやイベントループが、計算そのものが重いCPUバウンドにはコア数を活かした並列実行が向きます。Webアプリの多くはI/Oバウンドで、「待っている間に次のリクエストを受ける」だけで大きく性能が上がります。
実現方法の比較
| 方式 | 特徴 | 代表例 |
|---|---|---|
| マルチプロセス | メモリが独立して安全だが重い | [[プロセスとスレッド]] のプロセス分離 |
| マルチスレッド | 軽いがメモリ共有による事故が起きやすい | Java、C# |
| イベントループ | 1スレッドで待ちを回す。I/Oに強い | [[Node.js]] |
| 軽量スレッド | 言語ランタイムが数万単位で管理する | [[Go]] のゴルーチン |
非同期プログラミング
非同期は「結果を待たずに次へ進み、できたら受け取る」書き方です。JavaScript の async/await が代表例で、互いに依存しない処理を直列に await するのは初心者がやりがちな性能低下パターンです。
const a = await fetchUser(); // 1秒
const b = await fetchOrders(); // 2秒 → 直列なので合計3秒
const [a2, b2] = await Promise.all([fetchUser(), fetchOrders()]); // 同時なら2秒
よくある落とし穴
- 競合状態: 複数の処理が同じデータを同時に書き換え、結果が実行順に左右される
- デッドロック: 互いに相手のロック解放を待ち続け、永久に止まる
- イベントループのブロック: 1スレッド方式で重い計算をすると、全リクエストが止まる。時間のかかる処理は [[メッセージキュー]] に投げて別プロセスに任せるのが定石
初学者向けポイント
- まず「この遅さは待ちか、計算か」を切り分ける。手段の選択はそこから
- 共有データを減らすのが最良の対策。共有しなければ競合も起きない
- 並行にすると処理の順序は保証されない。順序が必要なら明示的に制御する
関連技術とのつながり
- [[プロセスとスレッド]] — 並行処理を支えるOSレベルの単位
- [[Node.js]] — イベントループによる非同期I/Oの代表例
- [[Go]] — 軽量スレッドで並行処理を書きやすくした言語
- [[メッセージキュー]] — 重い処理を切り離して非同期に実行する仕組み
- [[ブラウザのマルチプロセスアーキテクチャ]] — ブラウザ側のJSも1本のメインスレッドで動くため、重い処理でそこを塞ぐと画面が固まる。イベントループを塞ぐ問題がクライアント側に現れた形
Q: 並行(Concurrency)と並列(Parallelism)の違いとして正しいのはどれ?
- [x] 並行は切り替えながら進めること、並列は複数を同時に実行すること
- [ ] 並行はCPUを2個以上使い、並列は1個で足りる
- [ ] 両者は完全に同じ意味である
解説: CPUコアが1つでも並行処理は可能ですが、本当の同時実行である並列には複数コアが必要です。
Q: 非同期I/Oの効果が特に大きいのはどのような処理?
- [ ] 大量の数値計算が中心のCPUバウンドな処理
- [x] DBや外部APIの応答待ちが中心のI/Oバウンドな処理
- [ ] メモリを大量に使う処理
解説: 待ち時間に別の仕事を進められるため、I/Oバウンドな処理で効果が大きく出ます。
Q: 1スレッドのイベントループ方式で避けるべきことはどれ?
- [ ] 複数のAPI呼び出しを同時に走らせる
- [x] 重い計算処理をその場で実行してループを塞ぐ
- [ ] 結果を待たずに次の処理へ進む
解説: 1スレッドを占有すると全リクエストが止まります。重い処理はキュー経由で別プロセスに任せます。