コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
非同期と並行
非同期と並行
このレッスンで分かること
- 並行(concurrency) は「複数のタスクを進行中の状態に保つこと」、並列(parallelism) は「同時に物理的に実行すること」と区別されます
- 非同期処理は「I/O を待つ間に他の仕事を進める」モデルで、シングルスレッドでも高いスループットを実現します
- コールバック → Promise → async/await とアプリ層の表現は進化してきました
非同期と並行 とは
非同期と並行。本レッスンでは、非同期と並行 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
非同期・並行・並列の違い (要約)
- 並行 (concurrency) = 複数タスクを「進行中の状態」に保つこと。1 コアでも成立
- 並列 (parallelism) = 複数 CPU コアで「同時に物理的に」実行すること
- 非同期 (async) = I/O 完了を待たず、他のタスクを進めるプログラミングモデル
並列は並行の特殊形ですが、非同期は 1 スレッドの並行を支える 主要な実装手段です。
同期 / 非同期 / 並列 ざっくり比較
| モデル | スレッド数 | I/O 多重 | CPU 多重 | 代表例 |
|---|---|---|---|---|
| 同期 + 1 リクエスト 1 スレッド | 多 | 弱 | OS 任せ | 古典 Apache prefork |
| 非同期イベントループ | 1 | 強 | 弱 | Node.js、nginx、Python asyncio |
| async/await コルーチン | 1 〜 コア数 | 強 | 中 | Python async、Rust tokio、C# async |
| マルチスレッド | コア数〜 | 中 | 強 | Java サーバー、ゲームエンジン |
| マルチプロセス | コア数〜 | 中 | 強 (GIL 回避) | Python multiprocessing |
並行と並列の違い
- 並行(concurrency) 同時に進行中のタスクが複数あれば成立。シングルコアでも実現可能
- 並列(parallelism) 物理的に複数の CPU コアで同時実行する状態
料理の比喩でよく説明されます。1 人のシェフがフライパン 3 つを交互に振るのが並行、3 人のシェフが 3 つのフライパンを同時に振るのが並列です。
非同期 I/O のモデル
ネットワークやディスクの I/O は CPU に比べて圧倒的に遅い ため、待ち時間を有効活用する仕組みが鍵です。
| モデル | スレッド数 | 同時接続 | 例 |
|---|---|---|---|
| 同期 1 接続 1 スレッド | 多 | 数千が限界 | 古典 Apache prefork |
| 非同期イベントループ | 1 | 数万 | Node.js、nginx、Python asyncio |
| async/await(コルーチン) | 1 ~ コア数 | 数万 | Python async、Rust tokio、C# async |
非同期モデルは「I/O 要求を投げ、完了通知が来るまで他のタスクを進める」というスタイルで、1 スレッドで数万接続を捌けます。
具体例 (Python asyncio)
Python
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as resp:
return await resp.text()
async def main():
async with aiohttp.ClientSession() as session:
urls = [f"https://example.com/{i}" for i in range(100)]
results = await asyncio.gather(*[fetch(session, u) for u in urls])
print(len(results), "pages")
asyncio.run(main())await を書いたところで関数が一時停止し、I/O が完了するまでイベントループが他のタスクに切り替わります。同期版より 10 ~ 100 倍速いことも珍しくありません。
コルーチン vs スレッド
| 観点 | コルーチン | スレッド |
|---|---|---|
| 切替えの主体 | ランタイム(協調的) | OS(プリエンプティブ) |
| 切替えコスト | 数 ns ~ 数十 ns | 数 マイクロ秒 |
| メモリ | スタック数 KB | 8 MB(デフォルト) |
| 同時数の上限 | 数十万 | 数千 |
協調的とは「自分が await するまで他に譲らない」スタイルで、ランタイムが切替えポイントを完全に把握できるためロックが要らない局面が増えます。一方、CPU バウンドな処理を await 無しで書いてしまうと全タスクが止まる落とし穴があります。
並列処理の道具
- マルチスレッド OS スレッドを CPU コア数分立ち上げる
- マルチプロセス プロセスを並列に走らせて GIL を回避(Python など)
- SIMD 1 命令で複数データを処理する CPU 機能(AVX、NEON)
- GPU 数千コアで巨大な並列計算
トレードオフ
- 非同期は I/O 多重で強いが、CPU バウンドな処理には効きません(むしろブロックして悪化)
- スレッドプールは古典的ですが、コンテキストスイッチコストが上限を作ります
- async/await は伝染性(呼び出しチェーンが async でないと使えない)があり、既存コードベースの導入は段階的に進める必要があります
よくある誤解
- 「並行 = 並列」――別概念です。並列は並行の特殊形ですが、並行は並列を必要としません
- 「async/await なら速い」――待ち時間が支配的な処理にだけ効きます。純粋計算では同期処理と同じか、むしろランタイムオーバーヘッドで遅くなります
やってみよう
Python
import time, requests
urls = [f"https://example.com" for _ in range(20)]
t0 = time.time(); [requests.get(u) for u in urls]; print("sync:", time.time()-t0)同期版と上記の asyncio 版を比較すると、I/O 多重の威力が体感できます。
まとめ
このレッスンの要点は、並行と並列の区別 / 非同期 I/O モデル / コルーチンとスレッドのコスト の 3 点です。実務でこれらを切り分けて説明できるようになると、設計レビューや障害対応の精度が一段上がります。
理解度チェック
Q. 非同期処理 (async/await) が最も効果を発揮するワークロードはどれ?
- 巨大行列の数値計算 (CPU バウンド)
- 数万件の API リクエストを同時に投げる (I/O バウンド)
- シングルスレッドの逐次データ変換
- GPU での画像生成
答えを見る
正解は 2。非同期は I/O 待ち時間を有効活用するモデル。CPU バウンドな計算では、むしろランタイムオーバーヘッドで遅くなることもあります。
出典 (References)
最終更新: 2026-05-28
関連レッスン
よくある質問
Q. このトピックは実務でどう役立ちますか?
A. DB のクエリ最適化、API 設計、データ構造の選択など、設計判断の根拠になります。表面的にライブラリを使うだけでなく「なぜそれが速いのか」を理解できると、性能問題を未然に防げます。コーディング面接でも頻出のテーマです。
Q. 計算量はどう求めれば良いですか?
A. ループのネストごとに掛け算する、再帰なら漸化式から解く、というのが基本です。Big-O 表記は定数倍と低次の項を無視するため、n の指数(n²、n log n など)に注目してください。最悪・平均・最良の 3 つを意識すると説得力が増します。
Q. 覚えるべき定番アルゴリズムは何ですか?
A. 二分探索、クイックソート/マージソート、BFS/DFS、ダイクストラ、DP の基本問題(フィボナッチ・ナップサック)が必修です。これらを「白紙から書ける」状態にすると、応用問題が一気に解けるようになります。
次のレッスン
次は イベントループと epoll で、非同期と並行 を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- 非同期と並行 の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. 非同期と並行 とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン