node.js とは何か (2)
昨日に引き続き、いざ!part2なのだ。
前回では node.js と v8 の結びつきまでを書いたので、今日は Non-Blocking I/O の話を。
Non-Blocking I/O という言葉からブロックしない I/O をイメージするのはたやすい。でもこれを実現しようとなるといろいろとまあ面倒くさいんだよね。
それを解決する常套手段で言うとファイルディスクリプタ(ネットワークならソケットだね)を開いてそれをselectシステムコールの監視対象に加えておき、selectを呼び出すことで監視するっていう方法がある。こうすると何が嬉しいのかファイルディスクリプタが2つある場合で考えてみよう。
まずAとBというファイルディスクリプタを監視対象とする。
selectシステムコールを呼び出し、そのどちらかが読み出し準備完了となっていないかを確認する。
もしどっちも準備できていなかったらプロセスをスリープさせる。もしくは他にできることがあるのならその処理を行う。
で、どちらかが読み出し準備完了となったら読み出し処理を開始する。
A、もしくはBのどちらが先でも大丈夫ってところがまず嬉しい。
待っている間に他のことができるってところも嬉しいよね。
で、こうすることでユーザからすれば「ブロックしていないんじゃね?」ってことになるんだ。ま、他にも方法はあるんだけど。
ここで問題点が浮上する。
selectシステムコールってファイルディスクリプタの数が増えるほど性能が劣化しちゃうんだ。
そうなるとスケーラビリティを目指す上で問題があるってことになっちゃうね。
だからlinuxではepollっていうもっと高速なものが開発された。ところがFreeBSDやOSXにはkqueue、Solarisには/dev/pollという同じような仕組みを持つものが別々に用意されてしまったんだ。あーもうめんどくさい。普通に考えればソースの中でマクロ使いながらそれぞれ分岐させて別個の処理をするしかない。それを独自にちゃんとやってるソフトウェアももちろんあるけどね。nginxもそうだし、mikioさん作の Kyoto Tycoon もそうだ。
で、このめんどくさいっていう問題を解決するため、またもっと易しいインターフェースを提供するために作られたのがlibeventだ。
libeventはコンパイルするプラットフォーム上で最適なシステムコールを選択し、プログラムには同一のインターフェースを提供してくれる。
おなじみのmemcachedでも使われているね。
さあここで話は node.js にようやく戻るよ。
libevent を使っても良かったんだろうけど、開発当時、libev という libevent と同じような機能を提供する、しかももっと高速なライブラリがあった。libevent のバージョン2系は1系に比べてかなり高速化しているんだけど当時はアルファ版がリリースされるかされないかといった時期。「高速に動作する」という理念から libev を選択するのは自然の成り行きだったんだろうね。さらにうまいことに libev 側の利点として同じ作者が開発した libeio という非同期I/Oライブラリまでが存在していた。
だから node.js ではI/Oのイベントループ制御と非同期I/Oに libev と libeio を採用することなったんだ。
ところでこの libeio ではスレッドプールが使われている。先ほどのAとBの例で言うとAの読み出し処理中にBが準備完了になった場合、Bも並行して読み出したほうが効率がいいからね。だから前回シングルスレッド云々と言っていた訳だけどこの部分においてはマルチスレッドを効率化のために限定的に利用している。でもプログラムを組むときにユーザーがスレッドを意識することはない使われ方だよ、ご心配なく。
さあ、こうして v8(JavaScript) + libev(I/O用イベント制御ライブラリ) + libeio(スレッドプールを用いた非同期I/Oライブラリ) という node.js のベースが整うこととなり、さらなる問題解決へと動き出すこととなる。
今回の説明はココまで。
なるべく易しく説明するように心がけた積もりなんだけど、やっぱり前回に比べると難しいね。まだまだ自分ももっと深く理解する余地があるんだろうと思う。ホントに隅から隅までわかっていれば易しく説明できるはずだから。
リファレンスをまとめておくね。
selectのマニュアル
selectのチュートリアル
libevのサイト
libevのベンチ。libeventの1系との比較がある。
libeventの名誉を守るため2系とlibevを比較したベンチ。その1、その2
libeioのドキュメント
PerlのEVモジュール。libevのperlラッパ
IO::AIOというperlモジュール。libeioが使われている。
Rev、rubyのlibevラッパ
pythonにだってラッパがある。
実はpythonってすごいことに標準ライブラリでepollやkqueueを使うことができる。
あとselectまわりやNon-Blocking I/Oについてはいい書籍があるんだ。
その書籍とは「UNIXネットワークプログラミング Vol.1 ネットワークAPI: ソケットとXTI」で恐らく本棚に置いてる人も多いんじゃないかな。故 W.リチャード・スティーブンス御大によるものだ。自分の知る限り、ファーストネームをイニシャルだけにしてミドルネームとラストネームを読ませる人ってこのくらいしか知らない。ま、それはいいとして、その6章が「I/O の多重化: select関数とpoll関数」、15章が「非ブロッキングI/O」となっていてまさに今回取り上げている話題そのものとなっている。