先日からRubyのスレッドの挙動に振り回されているわけですが、これまでに試したのはmingw32版のv1.8.7とv1.9.2だけでした。結論としては、これらの実装からマルチスレッドでWin32OLEオブジェクトの呼び出しを行うと、プロセス全体がブロックしてしまうようです。
それではJavaで実装されているJRuby や、.NET Frameworkで実装されている IronRuby の実装はどうなっているのだろうという疑問が湧いたので、今日はこれらの実装で先日のプログラムを流して、これらがどのような挙動を示すのかについて調べてみました。結論から述べると、これらの実装はWin32OLEのようなネイティブのライブラリを呼び出しても、他のスレッドがブロックすることはありませんでした。本家版のRubyと、JRubyやIronRubyは厳密にはスレッドの挙動が異なっているようです。
† JRuby編
まずはJRubyからです。
C:\>\jruby-1.5.3\bin\jruby --version
jruby 1.5.3 (ruby 1.8.7 patchlevel 249) (2010-09-28 7ca06d7) (Java HotSpot(TM) Client VM 1.6.0_20) [x86-java]
JRubyにはWin32OLEが標準添付ではないので、まずgemを使ってjruby-win32oleをインストールしてやる必要があります。
jruby-win32oleではThe JACOB Project: A JAva-COM Bridge のJACOBというライブラリを使ってJavaとCOM間のブリッジをしているようです。これは何かのときのために覚えておくと便利そうです。
C:\>\jruby-1.5.3\bin\gem install jruby-win32ole
JRuby limited openssl loaded. http://jruby.org/openssl
gem install jruby-openssl for full support.
Successfully installed jruby-win32ole-0.8.0
1 gem installed
Installing ri documentation for jruby-win32ole-0.8.0...
Installing RDoc documentation for jruby-win32ole-0.8.0...
早速、下記のプログラムを起動してみるとこんな感じで他のスレッドに邪魔されることなく動作しているのが分かります。
今回検証した中では僕が頭で思い描いた動作に最も近い動作をする実装です。
C:\>\jruby-1.5.3\bin\jruby jroletest.rb
waiting...
Tue Oct 26 17:08:59 +0900 2010:0:nil massage
Tue Oct 26 17:08:59 +0900 2010:2:nil massage
Tue Oct 26 17:08:59 +0900 2010:1:nil massage
Tue Oct 26 17:09:04 +0900 2010:0:nil massage
Tue Oct 26 17:09:04 +0900 2010:2:nil massage
Tue Oct 26 17:09:04 +0900 2010:1:nil massage
Tue Oct 26 17:09:09 +0900 2010:0:nil massage
Tue Oct 26 17:09:09 +0900 2010:2:nil massage
Tue Oct 26 17:09:09 +0900 2010:1:nil massage
SIGINT trapped
Tue Oct 26 17:09:14 +0900 2010:0:nil massage
Tue Oct 26 17:09:14 +0900 2010:0:terminated!
Tue Oct 26 17:09:14 +0900 2010:2:nil massage
Tue Oct 26 17:09:14 +0900 2010:2:terminated!
Tue Oct 26 17:09:14 +0900 2010:1:nil massage
Tue Oct 26 17:09:14 +0900 2010:1:terminated!
20.845sec.
ちなみにテストプログラムはjruby-win32oleを使っているのでrequireの追加と、名前つきパラメータでのメソッドの呼び出しをサポートしていないので、メソッド呼び出し部分を変更しています。
jroletest.rb
start = Time.now
require 'rubygems'
require 'jruby-win32ole'
require 'thread'
module MSMQ; end
WIN32OLE::const_load(WIN32OLE.new('MSMQ.MSMQQueueInfo'), MSMQ)
QUEUE_NAME = 'test'
MAX_THREAD = 3
@threads = ThreadGroup.new
@active = true
Signal.trap(:INT){
puts "SIGINT trapped\n"
@active = false
@threads.list.each{ |thread| thread.run }
}
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
msg = q.Receive(nil,nil,nil,5000)
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...\n"
@threads.list.each{ |thread| thread.join }
puts "#{Time.now - start}sec.\n"
† IronRuby編
次はIronRuby。こちらは標準でwin32oleが使えるので、特に何もインストールする必要はありませんが、やはり名前つきパラメータでのメソッドの呼び出しをサポートしていないので、その部分については変更してあります。こちらもJRubyと同様にスレッドの動作が他のスレッドの影響を受けていないことが分かります。動き自体は問題ないのですが、なぜかCtr+CをちゃんとトラップできずにExceptionを吐いてしまいました。
C:\>ir --version
IronRuby 1.1.1.0 on .NET 4.0.30319.1
C:\>ir iroletest.rb
waiting...
2010-10-26 16:49:28 +0900:0:nil massage
2010-10-26 16:49:28 +0900:1:nil massage
2010-10-26 16:49:28 +0900:2:nil massage
2010-10-26 16:49:33 +0900:1:nil massage
2010-10-26 16:49:33 +0900:0:nil massage
2010-10-26 16:49:33 +0900:2:nil massage
2010-10-26 16:49:38 +0900:1:nil massage
2010-10-26 16:49:38 +0900:0:nil massage
2010-10-26 16:49:38 +0900:2:nil massage
2010-10-26 16:49:43 +0900:1:nil massage
2010-10-26 16:49:43 +0900:0:nil massage
2010-10-26 16:49:43 +0900:2:nil massage
2010-10-26 16:49:48 +0900:1:nil massage
2010-10-26 16:49:48 +0900:0:nil massage
2010-10-26 16:49:48 +0900:2:nil massage
mscorlib:0:in `JoinInternal': Interrupt (System::Threading::ThreadAbortException)
from bbb.rb:37:in `join'
from bbb.rb:37
from bbb.rb:37:in `each'
from bbb.rb:37
iroletest.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
@threads = ThreadGroup.new
@active = true
Signal.trap(:INT){
puts "SIGINT trapped\n"
@active = false
@threads.list.each{ |thread| thread.run }
}
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
msg = q.Receive(nil,nil,nil,5000)
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...\n"
@threads.list.each{ |thread| thread.join }
puts "#{Time.now - start}sec.\n"