スレッドをまたいだ大域脱出はエラーになるのでスレッドのブロックぎりぎりに大域脱出しよう

当たり前と言えば当たり前なんですけど。

スレッドをまたぐ大域脱出

Threadブロックよりも外にあるcatch(:out_of_thread)へ脱出しようとするとArgumentErrorエラーになります。

pry(main)> catch(:out_of_thread) do
pry(main)*   Thread.start do  
pry(main)*     catch(:inside_of_thread) do    
pry(main)*       loop do      
pry(main)*         sleep 2        
pry(main)*         throw(:out_of_thread)        
pry(main)*       end        
pry(main)*     end      
pry(main)*   end    
pry(main)*   p Thread.list  
pry(main)*   Thread.list.each{ |th| th.join unless th == Thread.main}  
pry(main)* end  
[#<Thread:0x007f9dda0677a8 run>, #<Thread:0x007f9ddc2256b0 run>]
ArgumentError: uncaught throw :out_of_thread

スレッドをまたがない大域脱出

Threadブロックの内側にあるcatch(:inside_of_thread)へ脱出する場合には問題ありません。

pry(main)> catch(:out_of_thread) do
pry(main)*   Thread.start do  
pry(main)*     catch(:inside_of_thread) do    
pry(main)*       loop do      
pry(main)*         sleep 2        
pry(main)*         throw(:inside_of_thread)        
pry(main)*       end        
pry(main)*     end      
pry(main)*   end    
pry(main)*   p Thread.list  
pry(main)*   Thread.list.each{ |th| th.join unless th == Thread.main}  
pry(main)* end  
[#<Thread:0x007f9dda0677a8 run>, #<Thread:0x007f9dda426ee8 run>]
=> [#<Thread:0x007f9dda0677a8 run>, #<Thread:0x007f9dda426ee8 dead>]

これで、throwすることにより、スレッド内の処理を全てすっ飛ばすことができます。

参照

module function Kernel.#throw

Kernel.#catchとの組み合わせで大域脱出を行います。 throw は同じ tag を指定した catch のブロックの終わりまでジャンプします。
throw は探索時に呼び出しスタックをさかのぼるので、 ジャンプ先は同じメソッド内にあるとは限りません。 もし ensure節 が存在するならジャンプ前に実行します。
同じ tag で待っている catch が存在しない場合は、例外で スレッドが終了します。
module function Kernel.#throw

class ThreadError

  • クラスの継承リスト: ThreadError < StandardError < Exception < Object < Kernel < BasicObject
要約

Thread 関連のエラーが起きたときに発生します。

  • カレントスレッドを Thread#join しようとしたとき
  • Thread#join でデッドロックしそうになったとき
  • 終了したスレッドを Thread#wakeup あるいは Thread#run しようとしたとき
  • スレッドが一つしかないのに Thread.stop しようとしたとき
  • Kernel.#throw がスレッド内で Kernel.#catch されないとき
  • スレッドから return しようとしたとき
  • イテレータを与えずにスレッドを生成しようとしたとき
  • カレントスレッドの属するスレッドグループが freeze されているときに、スレッドを生成しようとしたとき
  • freeze あるいは enclose されているスレッドグループにスレッドを加えようとした時

class ThreadError

ThreadErrorじゃなくArgumentErrorでしたですね。なんでかな。
そういえばスレッド内で生じたエラーは、エラーとして扱われず終わっちゃうんですね、少し忘れていました。

例外発生時のスレッドの振る舞い

あるスレッドで例外が発生し、そのスレッド内で rescue で捕捉されなかっ た場合、通常はそのスレッドだけがなにも警告なしに終了されます。ただ しその例外で終了するスレッドを Thread#join で待っている他の スレッドがある場合、その待っているスレッドに対して、同じ例外が再度 発生します。

begin
  t = Thread.new do
    Thread.pass    # メインスレッドが確実にjoinするように
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

また、以下の 3 つの方法により、いずれかのスレッドが例外によって終 了した時に、インタプリタ全体を中断させるように指定することができま す。

  • 組み込み変数 $DEBUG を真に設定する(デバッグモード) ruby インタプリタを -d オプション 付きで起動した場合も同様。 (オプションの詳細に関してはRubyの起動 を参照)
  • Thread.abort_on_exception でフラグを設定する。
  • Thread#abort_on_exception で指定 したスレッドのフラグを設定する。

上記3つのいずれかが設定されていた場合、インタプリタ全体が中断されます。
class Thread