組み込みクラス: Thread
Threadの作成は例にあるnew
の他start
とfork
が使える。
t = Thread.new{sleep 0.5} p t.status # "run" sleep 1 p t.status # "false" 正常終了済みの意味 p t.alive? # "false"
Thread.stopでスレッドを停止させることが出来る。
これにより、ステータスはsleep
となる。
スレッド生成直後はrun
になってしまうが、直ちにsleep
に切り替わっているのがわかる。
t = Thread.new{ Thread.stop } p t.status # "run" sleep 1 p t.status # "sleep" p t.alive? # "false"
最下位にはwakeup
かrun
を用いる。
t = Thread.new{ Thread.stop puts "Bye!" } p t.status # "run" sleep 1 p t.status # "sleep" t.wakeup sleep 1 p t.status # Bye! false
スレッドの終了はkill
かexit
を用いる。またクラスメソッドのThread.kill
とThread.exit
も終了に使えるが、前者は引数に殺すメソッドを指定する、後者はカレントスレッドを殺すという違いがある。
t = Thread.new{ Thread.stop puts "Bye!" } p t.status # "run" sleep 1 p t.status # "sleep" Thread.kill(t) sleep 1 p t.status # "false" killしても異常終了にはならない。
exitはかなり挙動が異なる。
t = Thread.new{ Thread.stop puts "Bye!" } p t.status # "run" sleep 1 p t.status # "sleep" t.instance_eval("Thread.exit") # main終了 # TIPS: instance_evalは文字列を渡しても外側のコンテキストで実行されてしまう。 sleep 1 # 実行されない。 p t.status # 実行されない。
スレッドにはensure節が設置でき、終了直前やkill
されたりしたときに実行される。
t = Thread.new{ |t| begin Thread.stop puts "Bye!" ensure puts "killed. Bye!" end } p t.status # "run" sleep 1 p t.status # "sleep" t.kill sleep 1 # "killed. Bye!" p t.status # "false"
スレッドで例外が発生する場合がある。その場合は警告なく異常終了する。 但しrescue節を設置すると、例外が発生しても異常終了させず"false"のステータス(正常終了)とすることが出来る。
t = Thread.new{ |t| Thread.stop raise } p t.status # "run" sleep 1 t.wakeup sleep 1 p t.status # "nil" 異常終了
rescue節を設置してみると...
t = Thread.new{ |t| begin Thread.stop raise "Error!" rescue => e p e.message end } p t.status # "run" sleep 1 t.wakeup "Error" sleep 1 p t.status # "false"
スレッドはその動作を他からのスレッドの処理を待って再開させる、join
という機能がある。joinを待っているスレッドで例外が発生した場合、その例外は待機中のスレッドにも同様の例外が発生する。
t_c = Thread.new{ |t| raise "Error in child!" } t = Thread.new{ |t| begin Thread.stop t_c.join rescue => e p e.message end } p t.status # "run" sleep 1 t.wakeup # "Error in child!" sleep 1 p t.status # "false"
スレッドのデッドロックとは 存在する複数のスレッドすべてが停止状態となっている状態をいう。
t = Thread.new{ |t| Thread.stop # tスレッドをストップさせる。 } Thread.stop # mainスレッドのストップ
これにより以下のようなエラーが表示される。
thread.rb:22:in `stop': No live threads left. Deadlock? (fatal) from thread.rb:22:in `<main>'
killやexitではだめであくまで、stopさせた状態でないとプログラムは正常終了してしまう。
スレッドクラスのクラスメソッドはスレッド一覧を表示する機能や、メインスレッド、カレントスレッドを表示する機能を提供している。
puts Thread.list # #<Thread:0x007fffc5a52608> t = Thread.new{ |t| Thread.stop puts Thread.current # #<Thread:0x007fffc5a52608> } puts Thread.list # #<Thread:0x007fffc5a52608> puts Thread.main # #<Thread:0x007fffc5e532e8> t.wakeup
明示的に指示しなければ、mainスレッドは表示されないようだ。
他のスレッドの終了を待つjoinメソッドは、更にスレッドの評価結果も返してくれるvalue
が上位互換メソッド的に存在する。
t_c = Thread.new{ |t| sleep 5 "Yeah!" } t = Thread.new{ |t| Thread.stop puts t_c.value "Yeah!" puts "finish!" } sleep 7 t.wakeup sleep 1 p t.status # "false"
スレッドはハッシュと同じような機能を持ち、キーと値をペアを簡単に引き出せる。
t = Thread.current t[:place] = "ochanomizu" p t[:place] # "ochanomizu" p t.keys # [:__recursive_key__, :place]
Mutexは組み込みクラスとしては実装されておらず、標準添付ライブラリとしての実装でreuqireが必要である。
Mutexとは排他制御を行う仕組みのことで、通常のThread.newの内側でThread.exclusive
というクラスメソッドの実行によってできたスレッドを使用する。
このスレッドが実行されている間、他のスレッドは一切実行されることがなく、そのスレッドの動きだけに拘束される。
s_ex = Thread.new{ Thread.exclusive{ 5.times { print "S" sleep 1 } } } t_ex = Thread.new{ Thread.exclusive{ 5.times { print "T" sleep 1 } } } s_ex.join t_ex.join # "SSSSSTTTTT" と表示される。 puts "\n"
これをexclusiveさせないと、
s_ex = Thread.new{ 5.times { print "S" sleep 1 } } t_ex = Thread.new{ 5.times { print "T" sleep 1 } } s_ex.join t_ex.join # "STSTSTSTST" と表示される。 puts "\n"
これを更に柔軟に、一スレッド全体ではなく特定の処理を行うときに他のスレッドに侵入させない様ロックを行うのが`Mutex.new.lock
である。
Mutexインスタンスは制御を行いたい複数のスレッドに対して、1つのインスタンスで管理させる必要があるようだ。
@m = Mutex.new s_ex = Thread.new{ @m.lock 5.times { print "S" sleep 1 } @m.unlock } t_ex = Thread.new{ @m.lock 5.times { print "T" sleep 1 } @m.unlock } s_ex.join t_ex.join # "SSSSSTTTTT" と表示される。 puts "\n"
既にlockが行われているmutexはunlock状態が起こるまで待つことが出来る。また、ロック状態を確認するためにlocked? try_lock
というメソッドが用意されている。
@m = Mutex.new s_ex = Thread.new{ @m.lock 5.times { print "S" sleep 1 } @m.unlock } t_ex = Thread.new{ puts @m.locked? # "True" @m.lock 5.times { print "T" sleep 1 } @m.unlock } s_ex.join t_ex.join # "StrueSSSSTTTTT" と表示される。 puts "\n"
以上の例から、2つ目のスレッドが一度排他制御を行おうとするものの、1つ目のスレッドがロックをかけているため待機しているのがわかる。
最後にFiberについて少し触れる。
FiberはThreadと異なり、明示的に指定(resume
)しないと始まらないし、処理が終了しても勝手に親スレッドに戻ったり(yield
)しない。明示的な操作が常に必要になるのが特徴である。
f = Fiber.new do loop do puts "I am Child!" puts ">> Child -> Parent" Fiber.yield end end 3.times do puts "I am Parant" puts ">> Parent -> Child" f.resume end # I am Parant # >> Parent -> Child # I am Child! # >> Child -> Parent # I am Parant # >> Parent -> Child # I am Child! # >> Child -> Parent # I am Parant # >> Parent -> Child # I am Child! # >> Child -> Parent