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を組み合わせて、低コストな非同期処理を実現しています。
大まかな流れは下記のとおりです。
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を使って半自動で制御してあげたほうがパフォーマンスいいよね、という話のようです。