Rubyコンテキストスイッチの不思議
仕事上、ROMAに関わっているので、稀にはRubyもやります。
今回検証に使ったソースは此方
https://github.com/crumbjp/Personal/tree/master/study/ruby
こんな感じで動くはずです
gem install eventmachine bash run.sh
Rubyスレッドのコンテキストスイッチの話
Rubyのスレッドはご存じの様に、1.8系は疑似スレッド、1.9系ではネイティブスレッドだけれど並列処理はしない(逐次実行)実装となっている。
疑似スレッド(green threads)
OS上のスレッドを利用せずメインスレッドをRubyインタプリタで切り替えている。
本当の意味での並列処理を行わない。
コンテキストスイッチはsignalがトリガとなっており、特定出来ないタイミングで切り替わる(※と思っていた)
しかし特にExtensionの処理中にはsignal unsafeやmutexがあるかもしれない。
この辺りの挙動をしっかり把握しておきたかった。
検証コード(スレッド駆動)
やたら複雑ですが、、
スレッド1(t1),スレッド1(t2)を並列実行平行実行させてるだけです。(※指摘により修正)
rubyはこの辺りがセンシティブでいけない・・・
関数:test1 , test2 の実装による挙動の違いを調べます。
begin t1 = Thread.new { Thread.stop for i in [0,1,2,3,4] test1 i end } Thread.pass t2 = Thread.new { Thread.stop test2 } Thread.pass t1.run # Thread1 start sleep 1 t2.run # Thread2 start t1.join t2.join rescue p $! end
検証1 (Rubyの世界)
単純にスレッドの挙動を押さえておく
puts '=== Pure ruby mode ===' def test1(i) puts 'test1 ('+i.to_s+') start' sleep 1 puts 'test1 end' end def test2 puts 'TEST2 START' sleep 3 puts 'TEST2 END **** HERE ****' end
結果
=== Pure ruby mode === test1 (0) start test1 end test1 (1) start TEST2 START test1 end test1 (2) start test1 end test1 (3) start TEST2 END **** HERE **** test1 end test1 (4) start test1 end
ちゃんと(疑似)並列に動いています。
検証2 (Cの世界)
検証1のsleepをExtension(C実装)に持ってくるとどうなるでしょう?
puts '=== Extension mode ===' require 'myext' include MyExt def test1(i) myext1 i end def test2 myext2 end
VALUE method_test1(VALUE self,VALUE vi) { int i = NUM2INT(vi); printf("test1 (%d) start\n",i); fflush(stdout); sleep(1); printf("test1 end\n"); fflush(stdout); return Qnil; } VALUE method_test2(VALUE self) { printf("TEST2 START\n"); fflush(stdout); sleep(3); printf("TEST2 END **** HERE ****\n"); fflush(stdout); return Qnil; }
結果
=== Extension mode === test1 (0) start test1 end test1 (1) start test1 end TEST2 START TEST2 END **** HERE **** test1 (2) start test1 end test1 (3) start test1 end test1 (4) start test1 end
Extension実行部(TEST2 START => TEST2 END)は完全に排他されています。
考えてみると、これは当然の実装で
rubyから見るとExtensionの先にどの様な処理があるか解りません。特にmutexがあるとデッドロックしてしまう。
よってExtension中でコンテキストスイッチを行う訳にはいかない。
検証3 (Ruby&Cの世界)
一応、Extension同士が干渉している可能性を排除しておきましょう。
test1 の方はPure ruby
puts '=== Complex mode ===' def test1(i) puts 'test1 ('+i.to_s+') start' sleep 1 puts 'test1 end' end require 'myext' include MyExt def test2 myext2 end
VALUE method_test1(VALUE self,VALUE vi) { int i = NUM2INT(vi); printf("test1 (%d) start\n",i); fflush(stdout); sleep(1); printf("test1 end\n"); fflush(stdout); return Qnil; }
結果
=== Complex mode === test1 (0) start test1 end test1 (1) start test1 end TEST2 START TEST2 END **** HERE **** test1 (2) start test1 end test1 (3) start test1 end test1 (4) start test1 end
やっぱり変わりません。
検証4 (EventMachineの世界)
RubyといえばEventMachine!(?)という事でEventMachineの挙動も調べます。
ご存じの様にEventMachine先はselect , epoll の様な待ち受け処理です。
検証2、検証3の結果、待ち受け中はrubyは完全に止まってしまうハズ。
しかし現実は横にスレッド立てて別の処理をしたり出来ます。
何故だ!!?
まずは挙動を確認
puts '=== EventMachine mode ===' def test1(i) puts 'test1 ('+i.to_s+') start' sleep 1 puts 'test1 end' end require 'eventmachine' class Echo < EM::Connection def receive_data(data) send_data(data) end end def test2 puts 'TEST2 START' EM.run { EM.add_timer(3) { EM.stop } EM.start_server("0.0.0.0", 9999, Echo) } puts 'TEST2 END **** HERE ****' end
結果
=== EventMachine mode === test1 (0) start test1 end test1 (1) start TEST2 START test1 end test1 (2) start test1 end test1 (3) start test1 end test1 (4) start TEST2 END **** HERE **** test1 end
何故だーーー!!????
ruby自身がExtension内でのコンテキストスイッチを許していないのに
EventMachineの待機中にはちゃんとスイッチされてる!!
可能性としては
- Extension中にも条件を満たせばコンテキストスイッチをする
- EventMachineが何かしてる
多分2.なんだろうな・・・
という事でこれからEventMachineを追ってきます。
何か解ったら次回!!