ユーザ空間、カーネル空間#
IO モデル#
『UNIX ネットワークプログラミング』は五つの IO モデルをまとめています
- ブロッキング IO / Blocking IO
- ノンブロッキング IO / Nonblocking IO
- IO 多重化 / IO Multiplexing
- シグナル駆動 IO / Signal Driven IO
- 非同期 IO / AsyncAsynchronous IO
ユーザ空間のユーザバッファがカーネル空間のカーネルバッファ データが準備できるのを待っています
ハードウェアのハードウェアデバイスがカーネル空間のカーネルバッファ データを準備しています
ユーザ空間のユーザバッファがカーネル空間のカーネルバッファ データを読み取ります
ブロッキング IO / Blocking IO#
ノンブロッキング IO / Nonblocking IO#
IO 多重化 / IO Multiplexing#
ブロッキング IO でもノンブロッキング IO でも、ユーザはある段階で recvfrom を使ってデータを取得する必要がありますが、データが準備できるのを待つ必要があります。要するに、ブロッキング待機かノンブロッキング待機かの違いで、結局はこの一つだけを処理することになります。
しかし、複数のデータソースを同時に監視する場合、どのデータソースが準備できたらそのデータソースを処理するのか、これが IO 多重化です。
Linux ではファイルディスクリプタ file descriptor がすべてに関連付けられます。ここではネットワークソケットであり、複数の fd を同時に待機し、どれが準備できたらそれを処理します。
IO 多重化には select、poll、epoll などのさまざまな方法があります。select と poll は fd セット内に fd が準備できていることはわかりますが、具体的にどれかはわかりません。
IO 多重化 - select#
fd をバイナリビットに変換して保存します。例えば、fd=1 2 5 を監視する場合、最右の 1 2 5 ビットを 1 にマークし、カーネル空間に渡します。
対応するビットの fd が準備できている場合は 1 に再マークし、その他は 0 にします。ユーザ空間に渡されるビットマップを走査し、1 のビットは準備できていることを示します。
利点は非常にスペースを節約できることです。欠点は 1024 を超えることができず、fd_set のコピーが必要で、チェック時には全体を走査する必要があります。
IO 多重化 - poll#
poll は select の 1024 の制限を引き上げ、リンクリストで理論上は無制限に保存できますが、必要はありません。長くなるほど走査時にパフォーマンスが低下します。
IO 多重化 - epoll#
epoll は赤黒木を使用し、カーネル空間内で赤黒木を維持し、すべての監視が必要な fd を記録します。同時に準備完了リストを維持し、すべての準備完了の fd を保存します。
プロセス:ユーザ空間は epoll_create を使用して epoll インスタンスを作成し、epoll_ctl を使用して赤黒木に fd を追加または削除し、epoll_wait を使用して準備完了イベントを待ち、準備完了リストから準備完了の fd を取得します。
ある fd が準備できると、カーネルはそれを準備完了リストに追加します。epoll_wait は準備完了リストから準備完了の fd を取得するだけで、全体の fd セットを走査する必要はありません。
利点:fd の上限がなく、全体の fd セットをコピーする必要がなく、単一の fd と操作を渡し、準備完了の fd のみを返し、ep_poll_callback メカニズムを利用して fd の状態を監視し、すべての fd を走査する必要がありません。
IO 多重化 - イベント通知メカニズム#
監視している fd でイベント(データが読み取り可能、書き込み可能など)が発生すると、カーネルはコールバックメカニズムを通じて、epoll の ep_poll_callback を使用して epoll インスタンスに通知します。
カーネルは準備完了の fd を epoll インスタンスの準備完了リストに追加します。アプリケーションが epoll_wait を呼び出すと、すぐに準備完了リストから発生したイベントの fd を取得して処理できます。すべての監視している fd を走査する必要はありません。
実際には ET モードを使用し、新しいデータのみが通知され、通知されたデータについては、非ブロッキング読み取りをループして完了するまで行います。
IO 多重化 - web サービスプロセス#
シグナル駆動 IO#
シグナル駆動 IO は非ブロッキング IO モデルの一種です。
アプリケーションはシグナルハンドラを設定でき、カーネルが fd が準備できたときにこのシグナルをプロセスに送信します。プロセスがシグナルを受信すると、すぐに recvfrom を実行してデータを読み取ります。
利点:非ブロッキングで、積極的にポーリングする必要がありません。
欠点:データを読み取る際に依然としてブロッキングが正常であり、シグナル処理にはオーバーヘッドがあり、シグナルが失われる可能性があり、高い同時接続の中では IO 多重化ほど効果的ではありません。
非同期 IO#
非同期 IO はユーザが IO 操作を開始した後、すぐに戻り、ブロッキング段階はありません。
カーネルはデータが準備できるのを待つだけでなく、データをカーネル空間からユーザ空間にコピーする責任も負います。IO 操作が完了した後、カーネルはユーザが登録したコールバック関数またはシグナルを使用してユーザプロセスに通知します。
利点:ユーザプロセスが IO リクエストを発起してから完了通知を受け取るまで、完全に非ブロッキングです。
欠点:複雑です。
同期と非同期#
非同期 IO は二段階、つまりデータを読み取る段階でも非同期です。
Redis は単スレッドですか?#
Redis は単スレッドですか、それともマルチスレッドですか?
- Redis のコアビジネス部分であるコマンド処理は単スレッドです。
- 全体の Redis はマルチスレッドです。
Redis のバージョン選定プロセスでは、二つの重要なタイムポイントでマルチスレッドのサポートが導入されました:
- Redis V4.0:古い時間のかかるタスク(例えば、非同期削除コマンド unlink)を非同期で処理するためにマルチスレッドを導入しました。
- Redis v6.0:コアネットワークモデルにマルチスレッドを導入し、マルチコア CPU の利用率をさらに向上させました。
Redis のコアネットワークモデルは、Redis v6.0 以前は単スレッドで、epoll のような IO 多重化技術を利用してイベントループ内でクライアントの状況を継続的に処理していました。
Redis が単スレッドを選択した理由は?
- Redis はコマンド処理において純粋なメモリ操作であり、実行速度が非常に速いため、パフォーマンスのボトルネックはネットワーク IO にあり、実行速度よりもネットワーク IO の最適化に集中する方が重要です。
- マルチスレッドは過剰なコンテキストスイッチを引き起こし、逆に不必要なオーバーヘッドをもたらします。
- マルチスレッドを導入すると、スレッドセーフの問題が発生し、スレッドロックなどの安全手段を導入する必要があり、逆にパフォーマンスを浪費します。
Redis が実装したネットワークモデル#
この記事は Mix Space によって xLog に同期更新されました。元のリンクは https://blog.0xling.cyou/posts/redis/redis-4