以前 第1回Hokuriku.rb(北陸Ruby勉強会)に参加してきた。その時次回予告でIronRubyについて挑戦しているようなことをお話し、以下のような次回予告をした。
ただ、IronRubyの日本語周りのサポート状況について調べていると、泣きたくなる状況であることが分かったため、IronRubyを触る頻度は落ちている。
しかしせっかく分かったことなので、その一部「スレッド」ということで書いてみようと思う。
今回の実験で利用するコードは以下のものを使う
$flag = false
threads = [] #生成済みスレッドを保持する
3.times do
puts "create new thread"
threads << Thread.new do
loop do # 新しいスレッドは常に繰り返す
#本来はここに有意義な計算処理などがはいる
break if $flag #終了フラグが立つまで繰り返す
end
end
puts "sleep 10 sec"
sleep 10 #次のスレッドをつくるまで 10秒ほど待つ
end
puts "sleep 10 sec"
sleep 10 #10秒たったら、終了する
$flag = true
threads.each do |thread| #すべてのスレッドが終了するのを待つ
thread.join
end
puts "done"
上記のコードだと次のような挙動をする。10秒ごとに新しいスレッドを生成し、そのスレッドは計算をし続ける。つまり以下のような挙動となる。
- 新しいスレッドを生成する( Thread-A )
- メインスレッドは、 10秒待つ
スレッド-A は 無限ループで処理を解すする(本来なら計算) - メインスレッドは、新しいスレッドを生成する( Thread-B )
スレッド-A は 無限ループで処理し続ける - メインスレッドは、10秒待つ
スレッド-Aは、無限ループで処理し続ける
スレッド-B は 無限ループで処理を開始する - メインスレッドは、新しいスレッドを生成する( Thread-C )
スレッド-Aは、無限ループで処理し続ける
スレッド-B は 無限ループで処理し続ける
(残り割愛)
さて、この計算を Ruby 1.8.7 の処理系で、行うとどうなるか?環境は Windows XP の 2Coreマシンである。
御覧の通り、CPU使用率は 50%のままである。つまりスレッドが増加しても、1つのCPUでしか処理をしていない。これは C言語で実装されたRuby 1.8.7 ではスレッドが ユーザースレッドで動作しているためである。(あるいはグリーンスレッドと呼ぶのが正しいかもしれない)。このスレッド Ruby処理系が、頑張って見かけ上のスレッド切り替えを行っている。あるタイミングで、Ruby処理系が持つスレッド管理情報をもとにスケジュールしているのである。とくにファイルなどのIO入出力部分ではブロックしうるか?を確認しつつ、もしブロックしうるのであれば別のスレッドへリスケジュールするようなこともしている。 実際にソースコードを見てみると、rb_thread_fd_writable()でrb_thread_schedule()をしているのがわかる。
[ruby 1.8.7-p174 io.c 445行目付近]
/* writing functions */
static long
io_fwrite(str, fptr)
VALUE str;
rb_io_t *fptr;
{
long len, n, r, l, offset = 0;
FILE *f = GetWriteFile(fptr);
len = RSTRING(str)->len;
if ((n = len) <= 0) return n;
if (fptr->mode & FMODE_SYNC) {
io_fflush(f, fptr);
if (!rb_thread_fd_writable(fileno(f))) {
rb_io_check_closed(fptr);
}
[ruby 1.8.7-p174 eval.c 11298行目付近]
int
rb_thread_fd_writable(fd)
int fd;
{
if (rb_thread_critical) return Qtrue;
if (curr_thread == curr_thread->next) return Qtrue;
if (curr_thread->status == THREAD_TO_KILL) return Qtrue;
if (curr_thread->status == THREAD_KILLED) return Qtrue;
curr_thread->status = THREAD_STOPPED;
FD_ZERO(&curr_thread->readfds);
FD_ZERO(&curr_thread->writefds);
FD_SET(fd, &curr_thread->writefds);
FD_ZERO(&curr_thread->exceptfds);
curr_thread->fd = fd+1;
curr_thread->wait_for = WAIT_SELECT;
rb_thread_schedule();
return Qfalse;
}
次に、IronRubyを見てみよう。同様に IronRuby0.9.1.0 .NET は 2.0 / Windows XP の 2Coreマシンで試してみた。
なんと、1つ目のThread生成では 1CPU(50%)しか使用していないが2つ目のThread生成からは、2CPU分(100%)使用しているのがわかる。
IronRuby は Ruby言語上のThread を .NET 又は Windows の スレッドに対応させていたのである。
このとこから、IronRubyを使えば、マルチコアマシンのCPUを有効に利用できるのである。今までのC言語で実装されたRubyを用いてた場合には、Thread は、プログラミングパラダイムの変化のみで処理パフォーマンスの向上とはつながりにくかった。IronRubyを使えば、処理パフォーマンスも期待できると考える。
となると、気になるのが、Ruby 1.9 である。確か Ruby 2.0 に向けて ネイティブスレッドにマッピングを始めているはずである。ということで試してみた。
なんと、1CPU分しか使用しない。なんと Ruby1.9 では ネティブスレッドへマッピングをしているが、同時には1つのネイティブスレッドしか動作しないようにしているらしい。
いろいろ調べてみると、拡張ライブラリへの影響を考え、そのようになっているらしい。確かに、今までシングルスレッドで動作することを前提としていた、C言語でかかれた拡張ライブラリを動作させていくには、一つの案としてとらえることができる。
さいごに
スレッドをCPUリソースの抽象化と考えていたが、実際へのCPUの割り当ては、言語、処理系によって変わる。これを理解していないと、マルチコア環境を有効に利用するマルチスレッドプログラミングはできないと考える。






コメントする