[ruby-dev:43453] ThreadGroup の強化案
From:
Hidetoshi NAGAI <nagai@...>
Date:
2011-04-28 22:49:03 UTC
List:
ruby-dev #43453
永井@知能.九工大です.
ようやく少し時間ができたので,ThreadGroup の強化を考えたいと思います.
案を作ってみましたので御意見をいただけると嬉しいです.
# パッチは未完でごめんなさい.
# 「パッチができたら考える」というだけの意見でも
# coding 作業の優先順位を上げるモチベーションになります.(^_^)
============================================================================
[ ThreadGroup の強化プラン ]
*** はじめに ***
Ruby の ThreadGroup は,単なる Thread 集合の制御だけではなく,
Thread 生成後に ThreadGroup を変更できるという性質により,
手続きや Thread (生成される sub-thread を含む) に操作可能な文脈を与えたり,
簡易 sandbox 作成を支援したりすることも可能である.
しかしながら,現在の ThreadGroup のメソッド群 (と機能) は,
そうした利用をサポートするには十分とは言えない.
例えば,thread 群に処理を分散させて結果を得る場合を考える.
thread 群を ThreadGroup にまとめたとしても,
現在は ThreadGroup 全体に対するメソッドに欠けるため,
結局は thread 群のリストを得て個別にメソッドを呼ぶ必要がある.
これではせっかく ThreadGroup を使用していても,その価値が低い.
また,ネットワークを介しているなどで thread が処理途中で例外終了する可能性があり,
そのような場合はすぐに代替 thread を起動する必要があるとすると,
システムが滞りなく動作するには多数の thread の実行状況を監視し続ける必要がある.
現在の Ruby でこれを行うには polling で状態を追う必要があり,効率が悪い
(いつどの thread がどういう理由で終了するかわからないので join では待てないため).
ThreadGroup の強化プランでは,そうしたケースでの処理効率改善も含める.
*** 単純な Thread 操作メソッドの不足 ***
特定の ThreadGroup に Thread を生成するには,
Thread を生成した上で対象の ThreadGroup に移動するしかなかった.
しかしそれでは ThreadGroup を移動する前に
Thread の処理が進行してしまう可能性が高く,
それを回避するために Thread を一旦 stop しておいて
移動後に wakeup する必要があった.
この問題への対策として,ThreadGroup#new_thread を導入する.
実行には,現在の ThreadGroup で生成した thread を
レシーバの ThreadGroup に移動する権限を持つことが必要であるとする.
その他,例えば ThreadGroup に属する Thread すべての終了を待つようなメソッド
ThreadGroup#join のように,ThreadGroup を Thread 集合として操作するために
不足と思えるメソッドを追加する.
*** ThreadGroup の dominate ***
現在の ThreadGroup では,enclose された場合に
外部から Thread を導入する手段がない.
enclose とはそういうものだからという話はあるが,
enclose された ThreadGroup を監視する Thread (ThreadGroup) 側からすると,
少々心許ない制約である.
例えば,監視対象の ThreadGroup 内に管理用 Thread の類が必要となりうるなら,
enclose する前にそうした Thread を生成しておく必要がある.
また,もし何らかのトラブルで管理用 Thread が落ちてしまうと回復の方法がない.
そこで,ThreadGroup 間に dominate という関係を規定する.
ある ThreadGroup A が別の ThreadGroup B を dominate しているならば,
A に属する Thread は,B に,あるいは B から Thread を移動させることができる.
ただし,B だけでなく Thread の移動元/移動先にも A が移動権限を持たねばならない.
Thread の移動権限を持つのは ThreadGroup の操作権限を持つ場合で,
* 対象が制約無しの ThreadGroup
* 対象が domanated で,自身が対象を dominate している ThreadGroup の Thread
のいずれかの場合である.
dominate している ThreadGroup A との関係を除いては,
dominate されている ThreadGroup B は enclose されている場合と同じ制約を持つ.
なお,dominated → enclosed → frozen の関係は一方通行であり,
状態変更後は元に戻すことはできない.
*** Thread が持つアクセス権限 ***
現在の ThreadGroup には,
ThreadGroup 環境からの Jail Break を防ぐための enclose という機構があるが,
enclose された ThreadGroup 内の Thread の権限についてはきちんとした規定がない.
ThreadGroup を enclose する目的は,ThreadGroup によって与えられた文脈から
不用意に離脱してしまわないようにという安全策の意味もあるが,
特定の Thread 群を信用できないというケースもあるだろう.
そう考えると,enclose や freeze された ThreadGroup から
他の ThreadGroup に属する Thread を操作することは認めるべきではないと言える.
逆に,そうした操作を許すことができるのなら,
わざわざ enclose や freeze をする必要は乏しいと考える.
よって,enclose または freeze された ThreadGroup の Thread が,
権限のない他の ThreadGroup の Thread を操作することを禁止することが望ましい.
具体的には,Thread のインスタンスメソッドの内,
---------------------------------------------------
[]=, abort_on_exception=, exit, kill, terminate,
priority=, raise, run, wakeup
---------------------------------------------------
である (状態参照は許すが,状態変更は許さない).
しかし,互換性を失うためにこの制約を直接導入することは難しいだろう.
ゆえに,デフォルトでは従来通り制約はなしとしておき,
制約を設定管理するためのメソッドとして
ThreadGroup#lock, ThreadGroup#locked? を導入する.
この場合,enclose などをされていない ThreadGroup に
制約を付与することも可能ではある (ユースケースは乏しそうだが…).
*** ThreadGroup の thread_queue ***
冒頭で述べたような polling の問題を回避するために,
終了した thread を受けとるための thread_queue を ThreadGroup が持てるようにする.
thread_queue は,終了した Thread を
終了順にできるだけ早く楽に処理にかけるための機構である.
もちろん,動作させる thread の処理内容に
終了をレポートする処理を書いておくという方法もないわけではないが,
それを実装するとなると少々面倒であろう.
終了した thread が自動的に特定の queue に追加されるなら
(この処理は C レベルで Coding する必要がある),
管理側がその queue から読み出して実行する処理を書くだけで良く,
面倒な部分を隠蔽することができる.
管理対象となるのは Thread の集合であるから,
ThreadGroup にそうした queue を持たせるのは妥当と言える.
thread_queue は一つの ThreadGroup につき一つだけとする.
thread_queue のモードは,(1) 使用しない,(2) 例外を生じた Thread のみを追加する,
(3) すべての Thread を追加する,の3種類である
(デフォルトは「thread_queue を使用しない」).
メモリ消費の爆発を避けるため,デフォルトでは thread_queue のサイズを有限とし,
保持サイズを超えた場合は古い Thread から捨てることとする.
thread_queue のアクセスには権限が必要である.
自らが制約を持たないか,自らが対象の ThreadGroup を dominate している場合に限り,
対象の ThreadGroup の thread_queue へのアクセスを許すこととする.
*** ThreadGroup の固有データ ***
Thread に文脈を与えるためには ThreadGroup に文脈を規定するデータを保持させる.
もし新しい Thread が生成されても,自動的に現在の ThreadGroup に所属するため,
ThreadGroup による文脈が保持される (ThreadGroup を移動すれば,文脈も変更される).
これまでは Thread のように ThreadGroup に固有データを持たせるためのメソッドが
定義されてはいなかったため,Thread クラス同様に固有データを扱うメソッドを追加する.
*** メソッド一覧 ***
ThreadGroup.new()
ThreadGroup.new(hash)
ThreadGroup 固有データを設定する引数を追加
current ThreadGroup を親 ThreadGroup として保持する
ThreadGroup.current
Thread.current.group に同じ
ThreadGroup#add(thread)
ThreadGroup#list
従来と同じ
ThreadGroup#enclose
ThreadGroup#enclosed?
従来と同じ
ただし,dominated であれば enclosed? は true となる
ThreadGroup#lock
ThreadGroup#locked?
他の ThreadGroup に属する Thread の操作制約の設定と状態参照
ThreadGroup#[]
ThreadGroup#[]=
ThreadGroup#key?(key)
ThreadGroup#keys
ThreadGroup 固有データへのアクセス
現在の ThreadGroup がレシーバの操作権限を持たない場合には参照・変更は不許可
( 現在の ThreadGroup == レシーバの場合は操作権限あり )
ThreadGroup#new_thread(){ ... }
レシーバの ThreadGroup に新しい thread を生成 (Thread.newと同じ)
引数は Thead#new と同じ
実行は,現在の ThreadGroup からレシーバへの Thread 移動権限を持つことが必要
ThreadGroup#start_thread(){ ... }
ThreadGroup#fork_thread(){ ... }
レシーバの ThreadGroup に新しい thread を生成 (Thread.{start,fork}と同じ)
引数は Thead#new と同じ
ThreadGroup#new_thread と違って Thread#initialize を呼ばない
実行は,現在の ThreadGroup からレシーバへの Thread 移動権限を持つことが必要
ThreadGroup#dominated_by(threadgroup)
レシーバを引数の threadgroup によって dominate する
レシーバが enclose されていなければ enclose される
現在の ThreadGroup がレシーバと引数との両方に操作権限を持たなければ例外を発生
もし,引数の threadgroup == レシーバなら,例外を発生する
ThreadGroup#dominate?(threadgroup)
レシーバが threadgroup を dominate しているなら true を返す
現在の ThreadGroup がレシーバと引数との操作権限を持たない場合には例外となる
現在の ThreadGroup == 引数の場合,enclosed 以上であれば権限なしとする
( enclosed 以上の ThreadGroup が自分の dominate 状態を調べるのを禁止するため )
ThreadGroup#dominated?
レシーバを dominate している ThreadGroup を返す
現在の ThreadGroup がレシーバの操作権限を持たない場合には例外となる
現在の ThreadGroup == レシーバの場合,enclosed 以上であれば権限なしとする
( enclosed 以上の ThreadGroup が自分の dominate 状態を調べるのを禁止するため )
ThreadGroup#join(exception=false)
ThreadGroup#join(limit, exception=false))
ThreadGroup のすべての thread が終了するのを待つ
引数 limit (秒単位の数値) が指定されていれば,その時間で timeout する
join を呼んだ後に生成された thread であっても終了を待つ
終了した thread を配列にして返す
引数 exception については,
nil であるときは,例外を生じた thread は破棄してそれ以外の hread のみを返す
true であるときは,例外の有無に限らずすべての thread を戻り値の配列に含める.
false のときは,例外を生じた thread に出会った時点でその例外を発生させる
ThreadGroup#values(exceptions=false)
すべての thread の戻り値を配列にして返す
値と thread との対応が取れないので,値だけが必要な場合に用いる
thread との対応が必要なら,join で得た thread の配列に対して each すること
引数が nil であるときは,例外を生じた thread は破棄してそれ以外の戻り値のみを返す
引数が true であるときは,例外は例外オブジェクトとして戻り値の配列に含める.
引数が false のときは,例外を生じた thread に出会った時点でその例外を発生させる
ThreadGroup#exit
ThreadGroup#kill
ThreadGroup#terminate
ThreadGroup のすべての thread を terminate する
kill を呼んだ後に生成された thread であっても terminate する
実体はこんな感じ
------------------------------------
current = Thread.current
Thread.exclusive do
until((lst = self.list - current).empty?) do
lst.each {|th| th.kill}
end
end
current.kill if current.group == self
------------------------------------
ThreadGroup#thread_queue_pop(non_block=false)
thread queue から終了した thread を一つ取り出す
ThreadGroup#thread_queue_mode
ThreadGroup#thread_queue_mode=(mode)
終了した thread を蓄える thread queue のモードの確認と設定
nil : 終了した thread を蓄えない (従来と同様の状態)
false or :exception : 例外で終了した thread だけを蓄える
true or :all : 終了した thread をすべて蓄える
ThreadGroup#thread_queue_length
ThreadGroup#thread_queue_length=(len)
ThreadGroup#thread_queue_limit
ThreadGroup#thread_queue_limit=(len)
thread queue の最大長を参照・設定する
thread 数の爆発を防ぐためのもの
最大長を超える場合は古い thread から捨てられる
デフォルトの長さは,配列のデフォルト長さと同じ 16 としておく
ThreadGroup#abort_on_exception
ThreadGroup#abort_on_exception=(state)
TreadGroup 内の thread での例外発生時にインタプリタ自体を終了させるか否か
ThreadGroup#raise
現在 ThreadGroup に所属するすべての thread に例外を発生させる
新たに生成された thread にまで例外を発生させる必要はなさそうなので、
実体は Thread.exclusive{threadgroup.list.each{|th| th.raise(...)}} とする
ThreadGroup#alive?
現在 ThreadGroup に所属する thread が存在すれば true
実体は threadgroup.list.empty? とする
ThreadGroup#wakeup
現在 ThreadGroup に所属する thread のすべてを起こす
実体は単純に Thread.exclusive{threadgroup.list.each{|th| th.wakeup}} とする
*** ThreadGroup には定義しない予定のメソッド ***
Thread にあるメソッドの内,以下は ThreadGroup での機能定義が難しいので
導入しない予定である.
ThreadGroup#value
複数の Thread が対象となるので,ThreadGroup#values で定義
ThreadGroup#run
現在 ThreadGroup に所属する thread のすべてを起こす
wakeup は意味があると思うが,run は疑問 (どれに制御を移すべきかが規定不能)
現在の thread list に順次 run をかけるという形はあり得るが...
ThreadGroup#priority
ThreadGroup#priority=
現在 ThreadGroup に所属する thread のすべてに対して設定する.
thread ごとに異なる可能性が高いので,参照は不可.
============================================================================
--
永井 秀利 (nagai@ai.kyutech.ac.jp)
九州工業大学大学院情報工学研究院知能情報工学研究系知能情報メディア部門助教