マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

nio4rでselect

TCPコネクションのようなIOを複数扱うとき、書き込みや読み込みが可能になっているものを一つ選びたい、ということがあります。RubyにはIO.selectというメソッドがあり、これを使えばそのようなことができます。

IO.selectは、Rubyが動く環境ならどこでも動くというメリットがあるのですが、selectの機構はカーネルによってはepollなどのより効率のいい方法が使えます。nio4rは、これらの効率のいいシステムコールないしCライブラリでを使ったselectを実現するライブラリです。

これ1年位前にちょっと触ったんですが、なんだかよくわかりませんでした。復習兼ねて使い方を説明します。

nio4rの使い方の大まかな流れ

1. NIO::Selectorオブジェクトを作る

2. 監視したいIOオブジェクトを登録する

3. NIO::Selectorで処理をブロックして待ち受ける

という感じです。

 

で、これの何が嬉しいの?という点なんですが、複数のIOを待ち受けて、処理が可能になったときだけ処理を行う、というようなことができます。普通にやったら無限ループを書いて、ステータスを確認して…というコードになるところを、無駄な処理を行わず、かつ無駄な待ち時間を発生させずに処理できます。

 

下記のサンプルコードでは、TCPで接続を待ち受けつつ、標準出力が書き込み可能になるのを待ちます。接続されない間は標準出力にドットを出力し続け、接続されたら、TCPソケットに送られてきた文字を出力します。

gist9cf98cd662a2d55c37d88c89278d5cc2

 

 動画です。左側でドットを吐き出し、データが送信されてきたタイミングで"!"が出力されるのがわかるかと思います。

そしてFiberへ

asyncというライブラリでは、Fiberとnio4rを組み合わせて、低コストな非同期処理を実現しています。

github.com

大まかな流れは下記のとおりです。

1. タスクをFiberで実行する

2. IO待ちが起こりそうになったら、IOをNIO::Selectorに登録する

3. IOとFiberの組み合わせを配列に保存する

4. Fiberの処理を中断する

5. selectする。このとき、他に処理可能なFiberがあればそっちを処理する

6. 中断していたIOがSelectされたら、3.の配列からFiberを取り出して再開する

 

こちらのほうがThreadを使った並列処理よりもパフォーマンスが良いそうです。Fiberは、Threadよりも低コストにスレッドが生成できます。Threadは、GVLの制約を受けてIOブロッキングが起きている間しか並列に処理が実行できません。ということは、より生成コストの低いスレッドをSelectを使って半自動で制御してあげたほうがパフォーマンスいいよね、という話のようです。