BLOGTIMES
2010/10/12

Ruby 1.8 と 1.9 のスレッドの違いにハマる

  ruby  mom 
このエントリーをはてなブックマークに追加

先日ちょっと書いたRuby から MSMQを使う話の続き。
このプログラムをちょっといじって、スレッドを使って複数のキューを待つようにしてみたら、うまく動かなくて色々試行錯誤したのでメモ。

結論から述べると、スレッドでやりたい場合にはRuby 1.9系に移行するのがいいみたいです。
どうしても1.8でやりたい場合には、forkなどを使ってマルチプロセス化しないといけなさそうな感です。

この対処方法は誤りです。現状のWin32OLEはマルチスレッド動作しません

サンプルプログラム

今回使ったサンプルプログラムはこんな感じ。
Receive時にタイムアウトを指定して、メッセージがなくても定期的に処理が戻ってくるようにしています。
これをやっておかないとスレッドが永久にブロックしてしまって、スレッドが終了できなくなってしまうので。

qget.rb

start = Time.now require 'win32ole' require 'thread' module MSMQ; end WIN32OLE::const_load(WIN32OLE.new('MSMQ.MSMQQueueInfo'), MSMQ) QUEUE_NAME = 'test' MAX_THREAD = 3 @active = true Signal.trap(:INT){ puts "SIGINT trapped" @active = false } threads = ThreadGroup.new MAX_THREAD.times do |n| t = Thread.new do mqi = WIN32OLE.new('MSMQ.MSMQQueueInfo') mqi.PathName = '.\private$\\' + QUEUE_NAME q = mqi.Open(MSMQ::MQ_RECEIVE_ACCESS,MSMQ::MQ_DENY_NONE) while @active puts "#{Time.now.to_s}:#{n}:wait\n" msg = q.Receive("ReceiveTimeout" => 1000) puts "#{Time.now.to_s}:#{n}:Label:#{msg.Label}, Body:#{msg.Body}, Priority:#{msg.Priority}\n" if msg puts "#{Time.now.to_s}:#{n}:nil massage\n" unless msg end puts "#{Time.now.to_s}:#{n}:terminated!\n" unless msg end threads.add(t) end puts "waiting..." threads.list.each{ |thread| thread.join } puts "#{Time.now - start}sec."

Ruby 1.8 で動かした場合

このプログラムは指定された数のスレッドを起動し終わると「waiting...」が表示されます。本来であればプログラム起動直後に「waiting...」が表示されるはずですが、上記スクリプトをRuby1.8で起動してみると、スレッドがうまく切り替わってくれないので「waiting...」が表示されるまでものすごい時間がかかります。Ruby1.8のスレッドは確か独自実装だったはずで、WIN32OLE経由で呼び出した先の部分はRubyの管理下ではないので、その部分でブロックするようなプログラムを書いてしまうとスレッドがうまく切り替わってくれないようです。

C:\Ruby187\bin>ruby --version ruby 1.8.7 (2010-08-16 patchlevel 302) [i386-mingw32] C:\Ruby187\bin>ruby.exe qget.rb 0:nil massage 0:nil massage 0:nil massage 0:nil massage 0:nil massage 0:nil massage 0:nil massage 0:nil massage 0:nil massage SIGINT trapped 1:terminated! 0:nil massage 0:terminated! 2:terminated! waiting... 10.202418sec.

Ruby 1.9 で動かした場合

これに対して、Ruby 1.9 からスレッドがネイティブスレッド実装になっているのでRubyの管理下にない部分でもスレッドが並列実行されるようです。
一見うまくいってるように見えたので勘違いしてしまいました。おそらく下記のGVLのせいだと思いますが、Ruby 1.9で実行してもRubyの管理下にない部分が勝手に並列に実行されることはないことが分かりました。
嘘を書いてしまってごめんなさい。

スレッド

ネイティブスレッドを用いて実装されていますが、 現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行される ネイティブスレッドは常にひとつです。 ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。 また拡張ライブラリから GVL を操作できるので、複数のスレッドを 同時に実行するような拡張ライブラリは作成可能です。

実際に実行してみるとこんな感じで、すぐにスレッドの起動が終わって各スレッドが並列に実行されています。

C:\Ruby192\bin>ruby --version ruby 1.9.2p0 (2010-08-18) [i386-mingw32] C:\Ruby192\bin>ruby.exe qget.rb waiting... 1:nil massage 2:nil massage 0:nil massage 1:nil massage 2:nil massage 0:nil massage 1:nil massage 2:nil massage 0:nil massage SIGINT trapped 2:nil massage 2:terminated! 0:nil massage 0:terminated! 1:nil massage 1:terminated! 12.230422sec.

Ruby 1.9はいろいろ変わっているので、このためだけに移行するのはちょっと気が引けるんですが、どうしようかなぁ。。。。


    トラックバックについて
    Trackback URL:
    お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
    このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3846
    Trackbacks
    このエントリにトラックバックはありません
    Comments
    愛のあるツッコミをお気軽にどうぞ。[policy]
    古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
    コメントはありません
    Comments Form

    コメントは承認後の表示となります。
    OpenIDでログインすると、即時に公開されます。

    OpenID を使ってログインすることができます。

    Identity URL: Yahoo! JAPAN IDでログイン