ユーザ空間、カーネル空間#
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