In article <1122611288.489329.28280.nullmailer / x31.priv.netlab.jp>,
  Yukihiro Matsumoto <matz / ruby-lang.org> writes:

> Rubyのスレッドについてそれほど知識がなくUNIX的伝統だけを知っ
> ている人の場合、IOオペレーションでブロックしないために
> nonblockingにするのではないでしょうか。connectの例はまさにそ
> れですね。つまり、noblockingにする人の中には明らかに「挙動が
> 変わる」ことを期待する人もいるわけです。また、nonblockingと
> いう名前を持つフラグを設定していながら、実際にはメソッドはブ
> ロックするというのも矛盾した状態ではあります。UNIXでの
> nonblockingフラグがシステムコールがブロックしないことを目的
> としたフラグである以上、「挙動が変わらない」ということは、
> Rubyにおけるnonblockingの意味を変えているということになりま
> すね。

そのような期待を持つ人がいるであろうことはその通りだと思います。そして、
UNIX 的伝統を知っている人が多いため、その知識を再利用可能にしたいとい
うのもわかります。

しかし、残念ながら、この UNIX 的伝統は、その知識を適用するというだけで
幸せになれる (アプリケーションがブロックするという問題を解決できる) と
いうほど単純なものではありません。そのため、単純に nonblocking な振る
舞いを提供することは、必ずしもユーザを幸せにすることにつながるとは限り
ません。

問題は、blocking のかわりに nonblocking にして得た時間に何の作業を行う
か、というところです。

nonblocking というのは、IO のシステムコール (典型的には read, write,
connect, accept) がブロックするかわりに EAGAIN を返すというものです。

# なお、write については EAGAIN 以外に割り込みがなくても partial write
# になるという効果もあります。

そのため、blocking だったらブロックして待っているしかなかった時間に他
の作業をできる、というのが nonblocking の利点です。

が、しかし、その時間に何の作業を行うか、というのが問題です。

ここで単純に呼出元に戻って続きの作業を行うというわけにはいきません。そ
れはシステムコールが行う作業が終わっていないからです。read ではデータ
を読み込んでいないわけですし、connect ではソケットがまだ接続済みになっ
ておらず、accept では client からの接続が得られていません。従って、そ
れらの結果を得てから行うべき作業はできません。write についてはバッファ
リングしておいて続きの作業に戻ることができるかもしれませんが、無限にバッ
ファリングするのでないかぎりどこかでブロックすることになりますし、
IO#sync=true であればその時点で全部書き終わることが期待されているでしょ
う。

というわけで、nonblocking を利用して意味のあることをするためには、呼出
元の続きの作業ではない作業を用意しておく必要があります。そのような作業
を用意しておく方法はいくつかあります。

* スレッド
  
  まず、スレッドがあれば複数の制御の流れを保持できますから、ひとつのス
  レッドがブロックしたら他のスレッドを動かせばいいわけです。これは
  Ruby 処理系が Ruby スクリプトに対して提供していることです。

* イベントドリブンフレームワーク

  また、各 IO についてイベント (read 可能になった・write 可能になった・
  connect が終了した・accept が可能になった) が起きた時に処理する作業
  をあらかじめ登録しておくという方法もあります。作業中に IO システムコー
  ルが必要になったら、とりあえずそのシステムコールを nonblocking で呼
  んでみて、普通に成功したら続きを実行し、EAGAIN や partial write になっ
  たら続きの作業を登録してイベントループに戻り、システムコールが可能に
  なってから再度呼ぶ、というわけです。これは connect に関しては Ruby
  処理系が内部でやっていることです。(なお、Ruby 処理系は read, write,
  accept に関してはシステムコールを呼ばないで直接イベントループに戻り
  ます。) また、Ruby で記述されたライブラリとして実装されることもあり
  ます。

* アプリケーション依存の何か?

  あと、可能性としてはアプリケーション依存で、いつでも開始できて短時間
  で終わる作業が存在するかもしれません。そういう作業があれば
  nonblocking で得た時間に実行することが可能でしょう。具体例は思い付か
  ないのですが、何か例を知っている人がいたら教えてください。

# nonblocking の用法として他の可能性を思いついた人がいたら教えてくださ
# い。

さて、nonblocking それぞれの使いかたについて、呼出元から見たときに適切
な挙動というのは異なります。

スレッドの場合には、呼出元から見ると、あたかも blocking であるかのよう
に振る舞うのが適切です。これは IO#write と IO#read の例がわかりやすい
と思いますが、スレッドで作業を分割している場合には他の作業というのは他
のスレッドにあるのであって、呼出元に EAGAIN が返ってきても行う作業があ
りません。したがって、結局その IO オペレーションを繰り返し行うことしか
できず、これは結局 blocking な挙動を Ruby スクリプトで再度実装している
ことになります。これは IO オペレーションを呼ぶときに nonblocking な挙
動を意識しないケースが多いことから陥りやすい罠になりますし、blocking
なときには処理系がやってくれることを自分で書かなければならないというの
はフラストレーションのたまる作業です。しかし、メソッドが nonblocking
な挙動を隠蔽し、blocking であるかのように振る舞ってくれればその問題を
Ruby スクリプトでは考えずに済ますことができます。

イベントドリブンフレームワークの場合には、呼出元から EAGAIN などの
nonblocking な振る舞いが見えることが必要です。これは、EAGAIN などが返っ
てこなければイベントループに戻れないからです。

つまり、IO 関連のメソッドの呼出元に対する振る舞いを変えることは、スレッ
ドなプログラムとイベントドリブンなプログラムのどちらかを幸せにし、もう
一方を不幸にすることになります。

ここで両方を幸せにするためには、スレッドなプログラムを幸せにするための
いつでも blocking なメソッドと、イベントドリブンなプログラムを幸せにす
るためのいつでも nonblocking なメソッドを用意するのがいいだろうという
のが私の意見です。

また、典型的には、nonblocking な振る舞いを呼出元に見せて役に立たせる
ためにはイベントドリブンフレームワークが必要になります。このことは、
nonblocking をちゃんと使うには、プログラム自身の構造から変えなければい
けなくて、実のところそんなに簡単に使えるものではないということを意味し
ます。

そして、スレッドとイベントドリブンフレームワークを比べると、スレッドは
組み込みであることもあってスレッドのほうが多く使われていると思います。
また、Ruby スクリプトでイベントドリブンフレームワークを記述するのはま
さに屋上屋を架すような話な上にプログラムの構造からして変えなければなら
なくてそれなりの覚悟がないとできないので、スレッドなプログラムを支援す
るほうが幸せになれる人が多いと思います。したがって、基本的には既存のメ
ソッドはなるべく blocking な振る舞いに変えていくほうが幸せなケースを増
やせます。そして、それとは逆に、スレッドなプログラムにとって適切な振る
舞いだった Socket#connect をあのように変えることは相対的に不幸なケース
を増やすことになるので反対です。

あと、Ruby における nonblocking の意味ですが、gets などの行単位の IO
オペレーションについては以前から nonblocking にしてあってもブロックし
ているわけですし、Ruby における nonblocking が一般にブロックしないこと
を意味しているとは感じません。実際の認識としては、ブロックしなくなるメ
ソッドもある、というあたりではないでしょうか。従って、nonblocking では
ブロックしないという意味を提供することにはあまり必要性を感じません。

なお、蛇足ですが、Ruby の read でも nonblocking なときにはイベントルー
プに戻る前にまず read するようにすれば、マルチスレッドでも
Linux 2.6 の /proc/loadavg を (nonblocking にすれば) 読めるようになっ
ていいんじゃないかと思います。

> 変えることがいけないわけではありませんが、変わっていることを
> 自覚する必要はあるでしょう。で、すべてを同時に満足することは
> できないので、どの辺を選ぶかですよね。

はい。選ぶために、デフォルトで nonblocking にしたときの話、IO#write の
例、Windows では動かないであろうという問題を示したり、こうやって長々と
文章を書いているわけです。

> |では nbconnect とか。
>
> connect_nonblockの方がマシですね。^^;;;

では、nonblock_connect とか。

あとは、生の connect という意味で sysconnect でしょうか。

ただ、sysconnect という名前は IO が blocking なときには blocking な挙
動が期待される名前なので、いつでも nonblocking な挙動のメソッドにより
イベントドリブンなプログラムを幸せにしようという狙いからは少し外れるの
ですが。あと、Windows で nonblocking な挙動にならないか、プロセス全体
が止まるというどちらかの問題がおきそうな気もします。
-- 
[田中 哲][たなか あきら][Tanaka Akira]