コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
イベントループと epoll
イベントループと epoll
このレッスンで分かること
- イベントループ は「I/O 完了イベントを 1 スレッドで順次処理する」設計パターンです
- Linux の epoll、macOS / BSD の kqueue、Windows の IOCP はその基盤となる OS API です
- これらを使うと 1 プロセスで C10K(同時 1 万接続)、C10M(1000 万接続)も視野に入ります
イベントループと epoll とは
イベントループと epoll。本レッスンでは、イベントループと epoll の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
多重 I/O API 比較
| API | OS | 計算量 | 通知方式 | 備考 |
|---|---|---|---|---|
| select | 全 UNIX | O(N) | レベル | fd 上限あり (FD_SETSIZE) |
| poll | UNIX | O(N) | レベル | fd 上限なし、走査は線形 |
| epoll | Linux | O(events) | LT / ET | C10K の救世主 |
| kqueue | macOS / BSD | O(events) | LT / ET | 多種イベント対応 |
| IOCP | Windows | O(events) | Completion-based | 非同期 I/O の元祖 |
| io_uring | Linux 5.1+ | O(events) | リングバッファ | システムコール削減 |
なぜ epoll なのか
古典的な多重 I/O のシステムコールは select と poll でした。
c
int select(int n, fd_set *r, fd_set *w, fd_set *x, struct timeval *tv);selectは監視する fd(ファイルディスクリプタ)の集合をビットマップでカーネルに渡し、何か起きたら全 fd をスキャンして返すpollは配列に変えただけで、本質は同じ「呼ぶたびに全 fd 走査」
fd が 10 個なら気にならないが、10 万個になると 線形時間が支配的 になり性能崩壊します。これが「C10K 問題」です。
epoll は Linux 2.6 で導入された イベント通知型 API で、登録は 1 回、以降は 何か起きた fd だけが返る 設計です。計算量が O(fd 数) → O(イベント数) に下がります。
API の流れ
c
int epfd = epoll_create1(0);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev);
struct epoll_event events[100];
while (1) {
int n = epoll_wait(epfd, events, 100, -1);
for (int i = 0; i < n; i++) {
handle(events[i].data.fd);
}
}epoll_create → epoll_ctl で fd を登録 → epoll_wait でイベントを取り出す、というシンプルな 3 ステップです。
図解
カーネルは「何か起きた fd リスト」だけをアプリに渡します。10 万接続中、実際に動いているのが 100 接続なら、ループは 100 回しか回りません。
レベルトリガとエッジトリガ
| モード | 通知タイミング |
|---|---|
| レベルトリガ (LT) | fd が読める間ずっと通知 |
| エッジトリガ (ET) | 「読めない → 読める」に変わった瞬間だけ通知 |
ET は高性能ですが、バッファを使い切るまで読み切らないと次の通知が来ない注意が必要です。nginx などは ET を使い、内部で while (read() > 0) ループを回します。
各 OS の対応 API
| OS | API |
|---|---|
| Linux | epoll、io_uring |
| macOS / FreeBSD | kqueue |
| Windows | IOCP(I/O Completion Ports) |
各言語の非同期ランタイムはこれらを抽象化して利用しています。Node.js は libuv、Python は selectors / asyncio、Rust は mio や tokio を介して下層 API を呼びます。
具体例
簡易チャットサーバーを Python の selectors で書くと、epoll の威力が分かります。
Python
import selectors, socket
sel = selectors.DefaultSelector()
def accept(sock):
conn, _ = sock.accept()
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read)
def read(conn):
data = conn.recv(1024)
if not data:
sel.unregister(conn); conn.close()
else:
conn.send(data.upper())
s = socket.socket()
s.bind(("0.0.0.0", 5000))
s.listen()
s.setblocking(False)
sel.register(s, selectors.EVENT_READ, accept)
while True:
for key, _ in sel.select():
key.data(key.fileobj)シングルスレッドのまま 1 万接続を捌けます。各接続にスレッドを割り当てたら数千で限界が来るのと対照的です。
io_uring
Linux 5.1(2019)で登場した次世代 API。epoll と違い、システムコール 1 回で 複数の I/O 操作をまとめて投入し、完了結果をリングバッファで受け取る 設計です。epoll よりさらにコンテキストスイッチを削減でき、データベースや高性能ネットワークアプリで採用が進んでいます。
トレードオフ
- イベントループはコールバック地獄に陥りがち。async/await を使えば視覚的にはほぼ同期コードと変わりません
- CPU バウンドな処理をループ内で実行すると 全接続がブロック されます。重い計算は別スレッドプールに逃がす設計が必須
- io_uring は強力ですが、セキュリティ脆弱性が複数報告された経緯から、本番投入は慎重に評価する組織が多いです
よくある誤解
- 「epoll があれば速い」――半分正解で、適切に使わなければ select と変わらないことがあります。LT モードで
EPOLL_CTL_MODを多発するとスケールしないなど - 「Node.js はシングルスレッド」――半分正解で、JavaScript 実行は 1 スレッドですが I/O ワーカーや libuv のスレッドプールが裏で動いています
やってみよう
ターミナル
strace -e epoll_wait,epoll_ctl -p $(pgrep -f nginx | head -1)実行中の nginx プロセスにアタッチすると、epoll_wait を中心に動いている様子がリアルタイムで見られます。これが現代の Web サーバーの心臓部です。
まとめ
このレッスンの要点は、select/poll の限界 / epoll の O(events) / レベルトリガとエッジトリガ の 3 点です。実務でこれらを切り分けて説明できるようになると、設計レビューや障害対応の精度が一段上がります。
理解度チェック
Q. epoll が select/poll より高速にスケールする理由はどれ?
- epoll はマルチスレッドで動くから
- epoll はイベント通知型で、何か起きた fd だけを返すから (O(N) → O(events))
- epoll はカーネルバイパスで動くから
- epoll は UDP 専用に最適化されているから
答えを見る
正解は 2。select/poll は毎回全 fd をスキャン (O(N))。epoll は登録 1 回 + 発生イベントだけ返す (O(events))。10 万接続中アクティブが 100 なら 100 回しかループしません。
出典 (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 セクションを読み返してみましょう。
関連レッスン