Develop and Download Open Source Software

View マルチスレッディングについて

category(Tag) tree

file info

category(Tag)
root
file name
multithread.ja.html
last update
2002-04-11 00:57
type
HTML
editor
None
description
NETRubyでのマルチスレッディング
language
Japanese
translate

マルチスレッドについてのメモ

デザイン

そろそろrequireの実装にかかる時期になったようだ。というのは後回しにして、スレッドセーフティについて考えて見た。

NETRubyのデザインゴールには、マルチスレッディングというのがある。これには次の2つの意味が含まれる。

  • NETRubyを同時に複数の(ホスト環境の)スレッドから操作可能(もちろん、同一インスタンス-MTA動作-をだ。STAについては、すでに問題ない)
  • Threadオブジェクトをネイティブスレッドにマッピングする

後者は、MSWin32版を使っている僕にとっては相当、意味があるんだが、実際にネイティブかどうかは、.NET Frameworkの実装に依存するんでわかんないけど(少なくてもGUIがからむとネイティブだってのは確認してあるが)。

で、後者を成り立たせるためには、前者も成り立つ必要がある。っていうのは、FrmaeやらScopeやらを完全にNetRuby(というオブジェクト)からThreadクラスに移してやる必要があるわけだが(候補は、オリジナルRubyのstruct threadから拾える)、同時に複数のThreadからevalが呼ばれたら、evalツリーポインタだって、スレッド毎に持つ(実際は、Parserオブジェクトに入れる予定)必要があり……とかやってけば、イヤでもNetRubyがスレッドセーフじゃなければならなくなるからだ。

って、厄介なんじゃないか? すべてのオブジェクトのiv_tblやm_tblも破壊系についてはスレッドセーフにしとかなきゃならないし(.NET Frameworkのコレクションについては、複数読み取り1書き込みについてはセーフということになってるから、読み取り系は一応、気にしなくて良いとして)今、コレクション使わずにprevとかでチェーン作ってる奴(RVarmapとか)はチェーンたどるのも危ないんじゃないか?(と思ったけど、RVarmapは先頭にプッシュしてくわけだから、問題ないか)

問題あるじゃん。 TLS使いまくりにしようと考えてたけど、ホスト側が異なるスレッドだったら、元々設定したデータが読めないわけじゃん。ということは、

thread:A -> a = new NetRuby();
thread:B -> a.eval("var = 3");
thread:A -> object o = a.eval("var"); // oは3を期待

とされた場合、NetRuby的にはどちらもRubyのメインスレッドでの処理だが、実際に実行しているスレッドは異なるわけだから、TLSから、FrameやScope抜いてくると、varは、スレッドBに属してしまい、明らかにホスト側が期待する動作はできないってことになる。

しかし、これまでのWin32でのユースケースを考えたら、NETRubyの同一インスタンスを複数のスレッドから触ることができなければ、嬉しくない。 (2/18時点の、スレッドローカル――inspectで使用――の使い方はバグってことだ。吐血)

って言うか、Threadは外部からはいじらせなければ良いからそれはそれでTLS使って良いことにしたとして、上のようにRubyとしてはシングルスレッド(Rubyのメインスレッドだけで処理)だけど、ホストはマルチスレッドという場合を考えると、Frame-Scope-BlockはThread(Rubyのオブジェクトとしての)ではなく、NetRubyのインスタンスが持つ必要があり、かつ切り替えにThread(Rubyじゃなく、.NET Frameworkのだ。ややこしいな)が持つアイデンティティをチェックしては「ならない」。ってことか。

そもそも、Rubyを埋め込んだホスト側がマルチスレッドという状況が現時点で無いわけだから、このへんは考慮外つーことなんだな。

  • 外部からの介入(EvalStringとか、Funcallとか)は、必ずNetRubyのメインスレッドのコンテキスト(端的に言えばFrameだ)で処理する

(ああ、そういえば、ASRはこれをやるために、IMessageFilter導入したんだっけ……)

と決めて、外部からのメイン以外の(Rubyの)Threadのインスタンスへの介入を防御することにして、RThread(以降、RubyのThreadクラスは、NETRubyの実装に合わせてRThread。.NET FrameworkのはThread)はTLSに突っ込むのではなく、NetRubyのフィールドとして展開(Hashかな。)させ、RThreadはTLSから自分のインスタンスを取れるようにしておき(外部から介入されないわけだから、メインスレッド以外は1対1になり問題なし)−いや、しなくても、HashのキーをThread.CurrentThreadにしてしまえば良いな)−自分がメインスレッドかどうかは、NetRubyのフィールドとしてメインスレッドのThread.CurrentThreadを突っ込み……とやると、外部ホストの異スレッドからの呼出しの時に困るから……

さて、どうやって、自分がメインスレッドかどうかを判断すれば良いでしょう?

というか、モデルが破綻してんじゃないか?

thread:A -> a = new NetRuby();
thread:B -> a.eval("var = 3");
thread:C -> a.eval("var = 3");

について、BとCはパーサは異なるから異なるevalTreeを作成できる。evalTreeの解釈は並行して行われるため、同一Scopeをシェアするわけなんだが、Scopeのlocal_varsへの書き込みはシリアライズしてやるから、単なる2重更新で済むからまあ良いとして、そもそも、こういうユースケースは無意味なんじゃなかろうか?

STAだと宣言した場合の問題点は? 本当に、上のような使用方法でMTA動作が必要なのか?

外部からの呼出しについては、NetRubyのthisポインタがある。だから、NetRubyをRThread派生クラスとしてやれば、自分がメインスレッドだということはわかる。だから、外部から呼ばれた場合、表層ではどうにかなるから、その時点でTLSに設定を行う。このTLSに設定した情報は、呼び出されたメソッドから抜けるまでを有効とする。 したがって、publicは徹底的に絞り込んでやれば良い。 というわけにはいかないか?

thread:A -> a = new NetRuby();
thread:B -> foo = a.eval("obj = Foo.new");
thread:C -> foo.Funcall("bar", arg1, arg2);

というシナリオを考えると、各オブジェクトのpublicメソッドにまで敷衍してやんなきゃならないうえに、オブジェクトはRThreadをまたがることができるから、保持しているNetRubyのインスタンスは共用される。したがってメインスレッドに縛ることはできない。

この時、foo::barは、メインスレッド(作成したのはA。fooをインスタンス化したのはB、barを呼び出したのはC)のコンテキストで実行する必要がある。RThradはNetRuby内部で作成するからA,B,Cのいずれにもならない。 RThreadのハッシュに存在しないスレッド=メインスレッドで判断可能だ。

だったら、RThreadにTLSはやっぱり使用可能ということになる。HashとTLSどっちが早いかだな。

でも待てよ?

thread:A -> a = new NetRuby();
thread:B -> th = a.eval("Thread.new { hogehoge }");
thread:C -> th.Funcall("stop");

でBが掴んでCに渡しているthってのは、RThreadじゃないのか? RThreadだが、問題ないはずだ。hogehogeは、NETRubyが作ったthread:Dで実行中。それに対してメインスレッドのコンテキストからstopを呼ぶのは問題ない。というか問題なければならない。だから、RThreadをTLSに入れるというのは、あくまでも自分の情報の管理手法に過ぎない。要はeval処理でScopeとかFrameを見る場合、どこから拾ってくるかだ。

なんというか、タワゴトだな。

ガチョーン! last_call_statusってスレッドセーフにできないって言うか、undefined-missingに入っちゃったらリエントラントに出来ないんじゃないかい? よく見たら、method_missingをf付きと内部用の2種類にしておきゃいいのか。

むむっerrInfoはどうしよう?

  • メインスレッド(=NetRubyを作ったスレッド)以外からの呼出しは、すべて暗黙裡にRThreadを作成して実行。

とすれば、矛盾がなくなる(スピードスターにはなれないけど)。この場合、例外情報はメソッドから抜けた時点で消えてしまう(というのは、暗黙に作成したRThreadを保存しておくと、そのスレッドが消失してもNETRubyからは判断できないため、広範囲にGCを阻止してしまいかねない=メモリーリークの元凶となる)。

だから、暗黙のRThreadを捨てる時点=メソッドから抜ける時に、errInfoをチェックして!=nullならば、例外を生成して、呼出し元に通知してやることにする。

NetRubyException : System.Exception
{
 public RException errInfo { get; } // Ruby's Exception
 public Exception InnerException { get; } // もし存在すれば
}

ってな感じだ。

  • Thread::critical
  • Thread::critical=
  • Thread#raise

は、NotSupportedException。


Last modified: Sat Feb 23 23:15:31 LMT 2002
copyright(c) 2002 arton, Under GPL