6.
defself.thread_main(idx) loop do #スレッド処理 end end defself.main(argv) th_infos = Hash.new foriin0…100 th_infos[i] =Thread.new(i){|idx| thread_main(idx)} end check_threads(th_infos) end
7.
defself.check_threads(th_infos) loop do th_infos.eachdo |i, th| f_restart = false caseth.status when“run”, “sleep” else f_restart = true end iff_restart th.kill th.join th_infos[i] = Thread.new(i) {|idx| thread_main(idx)} end end sleep1 end end
13.
classThreadWatch definitialize(timer) @update_time = nil @alive_timer = timer @mutex = Mutex.new end defupdate @mutex.synchronizedo @update_time = Time.now end end def valid? @mutex.synchronizedo returnfalseif!@update_time.nil? && (Time.now - @update_time) > @alive_timer) end true end end
14.
defself.thread_main(idx, watch) loop do watch.update #スレッド処理 end end defself.main(argv) th_infos = Hash.new foriin0…100 w = ThreadWatch.new(600) th = Thread.new(i, w) do |idx, watch| thread_main(idx, watch) end th_infos[i] =[th, w] end check_threads(th_infos) end
15.
defself.check_threads(th_infos) loop do th_infos.eachdo |i, info| th, w = info f_restart = false caseth.status when“run”, “sleep” f_restart = true unless w.valid? else f_restart = true end iff_restart th.kill th.join th = Thread.new(i, w) do |idx, watch| thread_main(idx, watch) end th_infos[i] = [th, w] end end end end
18.
無限ループのテスト loop do がある限り、処理が返ってこないため、テスト確認ができない。
ループ処理は、変数やメソッドでループの実行判定をするようにする。
defself.thread_main(idx, watch) while run? watch.update #スレッド処理 end end defself.run? true end
19.
describe OedoThread, “thread_main”do class OedoThread @@counter = 0 def self.run? @@counter += 1 return false if @@counter > 3 true end end it “call 3 times” do m_watch = mock(“thread watch”) m_watch.should_receive(:update).exactly(3) OedoThread.thread_main(1, m_watch) end end
20.
まとめ “mission critical”なシステムでThreadを使う場合 Threadのチェックは、Thread.statusの状態チェックだけではなく、Thread側での更新チェックも実装する。 チェック結果が異常な場合には、Threadは再起動する。 loop do を使うとテストが書けないため、ループを実行するかどうかを判定するメソッド(または変数)を作成する。
・’aborting’で時間がかかることがあるんですね。。。やってみないとわからない情報なんでしょうねえ。ありがとうございます。
・ThreadとSocketの両資源が残っているときにThread#killで回収しようとすると、Socket資源がリークしたりするので、Thread#raiseなどで先にSocketを別の方法で回収、というのが「お行儀のよい」方法だと思います。でも、bindだと資源はリークしないかもだし、きれいごと言ってるだけかもしれません。次の機会があればbindに気をつけてみます。
・th_infosは、サンプルコードではHashのkeyにカウンタ-を使っているのですが、
複数プロセス × 複数スレッドの中で、固有の識別IDになる値をkeyにすることを想定しています。 →サンプルコードは分かりやすくするために、配列にした方がよかったですね。
・Thread#alive? だと、’aborting’ もtrueを返すので、異常なThreadを早く検知し、
再起動するために、statusを使っています。
statusの適切な更新が難しいというのは、知らなかったので、今後検討したいと思います…。
・Thread#kill については、Thread#alive? がfalse を返す場合ならよいのですが、
Thread#alive?がtrue(または、status が’run’ ) を返して、
Threadのループ内で更新させる情報(Slide内のThreadWatch#valid? )で
異常と判断された場合には、Thread#killは必要だと思うのですが、いかがでしょうか?
→各Thread で、socket をbind() しているような場合、
Threadを停止(kill)してからでないと、bind()エラーが出ていた記憶があります。
・Thread#statusを’run’もしくは’sleep’で比較するのでなく、Thread#alive?を呼んでほしいです。JRubyのようなGILなし環境だと、statusを適切に更新するのが難しいので。。。
・今の構成だと、Thread#kill -> Thread#joinが絶対に刺さらないようにする必要があります。ensureでIO#closeとかやったり、ログ書き出し→その先のMutex取れず、などのパターンで刺さると、メインスレッドが止まるので。Thread#kill!だとリークする可能性もあるので、daemon系ではThread#kill系列は基本避けたほうがよいと思います。
・と、ここまで書いておいてなんですが、!th.alive?であれば、killとか意味ないかも?