コンビニでわかるノンブロッキングIO

NginxやNetty、Node.jsなど日常的に利用されているミドルウェアでも主流になりつつあるのにいまひとつ正しく理解されているのかどうか怪しいノンブロッキングIOですが、その概念について社内の技術共有会でコンビニを題材に説明していたのが面白かったので少しアレンジして紹介してみたいと思います。

ここではスレッド、CPU、リクエストを以下のように表現することにします。

  • 店員=スレッド
  • レジ=CPU
  • 客=リクエスト

1. シングルスレッド×ブロッキングIO

まずは最も単純なシングルスレッド×ブロッキングIOです。図にするとこんな感じです。

f:id:takezoe:20151008020355p:plain

店員(スレッド)が1人しかいないので同時に1人のお客さんしか処理できません。また、現実にはあり得ませんが、店員はアルバイトを始めて間もないのか、お弁当の温め中も電子レンジに張り付いており、温め終わるまで次のお客さんは待たされてしまいます。

2. マルチスレッド×ブロッキングIO

続いてマルチスレッド×ブロッキングIOです。ApacheなどノンブロッキングIOが登場する前のサーバアプリケーションはこのモデルでした。

f:id:takezoe:20151008020359p:plain

単純にスレッド店員(スレッド)を増やすことで同時に処理可能なお客さんの数が増えました。店員の数を増やせば増やすほど同時に処理可能なお客さんを増やすことができますが、増やすことができるのはレジの数(CPU)までとなります。それ以上の店員がいても基本的には意味がありません。

(実際のサーバアプリケーションでもCPUのコア数以上のスレッドを使用するとスレッド間のコンテキストスイッチが発生してオーバーヘッドになるためスレッド数を増やしすぎると逆効果になります)

3. シングルスレッド×ノンブロッキングIO

そこで登場するのがノンブロッキングIOです。店員さんがスキルアップし、お弁当の温め中に次のお客さんの会計を行うことができるようになりました。

f:id:takezoe:20151008020403p:plain

お弁当の温め中は店員が待ち状態になるのでこの間に次のお客さんの会計を行うことができます。これによって見かけ上、1人の店員さんが複数のお客さんを同時に処理できるようになるわけです。

ここで、会計はCPUを使う処理、お弁当の温めはIO処理と考えることができます。つまり、「IO待ちの時間を活用することで1スレッドで複数のリクエストを処理できる」という点がノンブロッキングIOを使用するメリットということになります。また、店員さんが電子レンジとの間を往復して温めを待っているお客さんにお弁当を渡す動作がノンブロッキングIO時のコンテキストスイッチで発生するオーバーヘッドと考えることができるかもしれません。

このように現実世界のメタファーに置き換えて考えてみると従来のスレッドモデル×ブロッキングIOによる並列化とノンブロッキングIOの違いが理解しやすいのではないかと思うのですが、いかがでしょうか?