Risseは一段落付いたのかとおもったのですが、思い切ってスレッドへの対応を進めています。
発端は Boehm GC とデストラクタ(ファイナライザ)にからむ問題から。
Boehm GC ではデフォルトでは、ファイナライザはコレクション中に呼び出されますが、これはコレクションを行っているスレッドから呼び出される事になります。コレクションは GC_gcollect() で明示的に呼び出される場合と、GC のメモリ確保関数内から呼び出される場合があります。どちらの場合も該当の GC の関数を呼んだスレッドでファイナライザが走ることがあります。
Risa/Risseの用途では、「メインスレッド以外では GC の関数を使わないでください」というのがちょっと難しいので、どうにしてもメインスレッド以外でもファイナライザが走ることになります。
メモリ確保中にファイナライザが走った場合、メモリ確保を行うルーチンとファイナライザのルーチン間で同じロックを使っているとデッドロックを起こす可能性がありますが、GC にはファイナライザの呼び出しをキューイングすることにより遅延する機能があるので、Risaではそれを用いてファイナライザ用スレッドを作ってファイナライザを少し遅延して呼び出すようになってます。しかしどうにしろメインスレッド以外からファイナライザが呼び出されることに変わりはありません。
デストラクタが別スレッドから呼び出されても良いようにコーディングするということは、芋づる式にほとんどの他の部分もマルチスレッドに対応しなければならないと言うことになります。
しかしまぁ、Risa はもともとかなりの部分がスレッド対応(複数スレッドからの同時アクセスに対して安全)になっていたので、Risse もこの際対応してしまおうかと言うことになりました。
Risseのここでいうスレッドというのはいわゆるグリーンスレッドのことではなくてネイティブスレッドの方です。ちょっと現状のRisseではむしろ構造的にグリーンスレッドに対応するのは面倒なので、ネイティブスレッドにのみ対応と言うことになります。
Risa/Risseは複数スレッドによる同時実行に関して、「Risseの処理系(コンパイラやVMなど)がクラッシュしない程度」の最低限の保証をする予定です。具体的には「メンバへのアクセスはアトミック(ただしインクリメントや += 演算子などはその限りではない)」「共有変数、配列などへのアクセスもアトミック」などです。それを超えるような保護などは自動的には行われません。Risaもこの程度の保証をするつもりです。
同期機構としては Java ライクな synchronized 文/修飾子をサポートしています。synchronized は(プリミティブ型以外の)オブジェクトに対してロックを行うことが出来ます。また、Thread クラスがネイティブスレッドの機能を提供します。そのほかの同期オブジェクト等も追加する予定。
たとえば、GCに、オブジェクトを作成したスレッドでファイナライザが動くようになるようななんかいい方法があればもしかしたらマルチスレッド対応は撤回するかもしれません。マルチスレッド対応による利点は大きいのかもしれませんが、現状のC++のような言語を使う以上、Risse処理系やライブラリの実装にも、またRisseスクリプトのコーディングにも(シングルスレッドしか考慮しなくて良かったときに比べて)かなりの負担をかける事になると思うので……。
もちろん、不安定性の温床にもなるでしょう。といっても吉里吉里2/TJS2では、「(GCが貧弱なので)循環参照でメモリリークが起こる」「参照カウンタが狂ってクラッシュあるいはメモリリークする」「マルチスレッドに対応していないのでマルチスレッドなライブラリからのコールバックが大変」などで悩みました。これらはRisa/Risseでは遭遇しなさそうな問題ですから、結局は悩む原因が変わるだけのことかもしれません。
スレッドに対応することになっても、Risa/Risseはマルチスレッドを使わなければ何も作れない、ということにはしたくないと思っています。ほとんどはシングルスレッドでも十分コーディングできて、必要ならばマルチスレッドプログラミングもできる、といった程度がよいと考えています。
あ、ちなみに前にも書いたような気がしますが、ファイナライザがどうのと言っていますが、 Risse には現状 TJS2 の finalize メソッドに相当するような機能はありません (オブジェクトの回収時に呼び出されるようなメソッドはありません)。
C++で簡単にRisseのメソッドやプロパティを書けるようにする Native Binder と呼んでいる物を作りました。
tRisseString tRisseHogeInstance::hoge(const tRisseString &str, int i1, int i2)
などと書いておいて
RisseBindMethod(this, RISSE_WS("hoge"), &tRisseHogeInstance::hoge);
とすると、メソッドの引数と戻り値を判断して、型の変換やら必須の引数の数のチェックやら RTTI のチェックやらをやるコードを、コンパイラがテンプレートから自動的に実体化してくれます。それをクラスに登録することができます。
ncbind や Xtal の機構によく似ていると思います。
C++の変態さを改めて実感したひとときでした。