
「Reactorパターン」は、イベント駆動(Event-driven)型の設計パターンで、サーバーやネットワーク通信など「I/O多重処理(非同期処理)」に使われる非常に重要なパターンです。
このデザインパターンの目的としては、次のような感じです。
・多数の入出力(I/O)イベントを効率的に扱う。
・同時接続をスレッド爆発させずに処理させる。
・各イベントの処理を「ハンドラ」に委譲する。
一言で言うと、「1つのイベントループで多数のI/Oイベントを効率的にさばくための設計構造」です。
基本構造
Reactor (イベントループ)
┌──────────┐
│ Demultiplexer │ ← OSが提供するselect/poll/epoll/kqueue等
└──────────┘
│
[イベント検出]
│
Dispatch
↓
[Handler呼び出し]
主要コンポーネント
| コンポーネント |
役割 |
| Reactor |
イベントを待ち受け、発生したイベントを対応するハンドラへディスパッチ |
| Handler |
各種イベント(accept / read / write)を処理 |
| Demultiplexer |
OSレベルでI/Oイベントを監視(`select()` や `epoll_wait()`など) |
処理の流れ(概略)
1. Reactor がイベントを監視 (select や poll を使用)
2. イベントが発生すると、対応するHandlerを呼び出す
3. Handler が実際の処理(受信・送信・応答など)を実行
サンプルコード
Node.jsのイベントループは、まさにReactorパターンの実装例です。
const net = require('net');
// === Reactor: イベントループでI/Oを待ち受け ===
const server = net.createServer((socket) => {
console.log('クライアント接続');
// === Handler: データ受信イベント ===
socket.on('data', (data) => {
console.log('受信:', data.toString());
socket.write('受信しました!');
});
// === Handler: 接続終了イベント ===
socket.on('end', () => {
console.log('接続終了');
});
});
server.listen(3000, () => {
console.log('Reactorサーバー起動中 (port 3000)');
});
Node.jsでは内部的に「libuv」というCライブラリがepoll/kqueueを使ってReactorパターンを実装しています。
メリット
・スレッド数が少なくても高い同時接続性能を持つ
・ノンブロッキングI/Oによる高スループット
・イベント処理を明確に分離できる(Handler単位)
デメリット
・コード構造がコールバック地獄になりやすい(特に言語サポートが弱い場合)
・イベント駆動モデルの理解が必要(命令型に慣れた開発者には直感的でない)
・各Handlerが重い処理を行うと、全体のイベント処理が滞る(非同期設計が重要)
関連パターン
| パターン |
違い・関連性 |
| Proactor |
Reactorが「イベント検出 → 処理開始」なのに対し、Proactorは「OSが処理完了後に通知」するパターン(例:WindowsのIOCP) |
| Observer |
ReactorはI/Oイベントに特化したObserverパターンの一種 |
| Event Loop |
Reactorの中核的構造。JavaScriptやPythonのasyncioもこの考え方を採用 |
あとがき
Nodeでは標準的に使う、Serverソケットの構造パターンですね。
イベント発火からのハンドラーへの接続は、Javascript特有で使用しますが、慣れていないとこの設計が乱雑になりがちです。
Reactorフレームワーク自体を参考にしなくても、ネイティブJavascriptでもクリックや、スクロールなどのイベント発火で応用できるので、Webエンジニアやアプリエンジニアであれば、理解しておくべきデザインパターンですね。
0 件のコメント:
コメントを投稿