読者です 読者をやめる 読者になる 読者になる

mrubyにThread実装のための機能を追加したい - その後

ちょっと時間があいてしまいましたが、mrubyにThread実装のための機能を追加する件のその後のお話をしたいと思います。

あのあと、そんなにいろいろやったら別の処理系になっちゃうよ(意訳)という意見を頂いたので、
なるべくやること絞ってやろうということで、本当に必要なのは何?ってことで調査しつつ実装をしていました。

とりあえず結果だけを観たい人は、下記のgithubのURLに開発途中のbranchを公開しています。
このさきの何をどうしたという話をskipされる方はコードを読んでもらうのが早いかと思います。

crimsonwoods/mruby · GitHub
crimsonwoods/mruby-thread · GitHub

mrubyへの変更

さてそれでは、ここからはmrubyに加えた具体的な変更の話をしようと思います。

まず、今回の設計・実装のポリシーとしては、

  • なるべくmrubyのcoreに手を入れない
  • Thread/Lock APIはmrbgemで置き換え可能
  • multi-thread対応しなくて良い場合にover-headが極力発生しない

というものです。
で、このポリシーにそってどうしたらminimumな変更になるかを考えました。
その結果、大筋では下記のような機能を実装するということにしました。

  • LockのためのAPIの入り口だけmrubyのcoreで定義
  • ThreadのためのAPIの入り口だけmrubyのcoreで定義
  • resource shared RiteVMの実装
  • resource shared RiteVMの生成と破棄のAPIをmrubyのcoreで定義

ちなみに、このとき課題になったのは以下の点です。

  • Lock APIとしてmrbgemに何を要求するか?
  • Thread APIとしてmrbgemに何を要求するか?
  • mrbgemが一切取り込まれていない状態でもcompileが通ること

それぞれの課題について少し説明したいと思います。

Lock APIとしてmrbgemに要求すること

multi thread環境下でのlockやexclusive accessを提供する方法はいくつもあって、
例えばよく知られたものにはmutexやsemaphoreなどのlock APIがあります。
それ以外にもatomicやthrough putへの影響を押さえやすいread-write-lockなどがあります。
mrubyは組み込み機器も動作環境のターゲットとして含めていますし、何よりmulti-platformに対応しなければなりません。
そのため数あるlock architectureから、組み込み環境のようなcheapな環境であっても使えることが見込めて、かつmulti-platformで動作するようなものを選択しなければなりません。

とはいえ、実際にmulti-threadで動作をさせるためには、なんらかのthreading APIと最低限のlock API(恐らくmutex程度)の存在は期待できるため、それほど難しく考えないことにしました。
それに、最近は組み込み機器といってもLinuxが乗り、POSIXAPIがそのまま使えることも多々ありますし、mrubyを載せるような規模の組み込みソフトウェアが何らかのOSの上で動くことを期待するのは、それほど無理な話ではありません。
ということで、今回は、API的にはread-write-lockの形をとり、mutexしか提供されていないような環境下ではmutexを使用してread-write-lockを実装してもらうか、lockのupgrade/downgradeを諦めてmutexとしても使えるように実装するという方針を採用しました。

Thread APIとしてmrbgemに要求すること

Threadとresource shared RiteVMのマッピングの管理のために、Thread APIとしていくつかの要件を満たすようにmrbgemに要求する必要がありました。
具体的には、以下の様なものです。

  • 現在のThreadの識別子を取得できる機能の提供
  • Threadの識別子同士の同値比較機能の提供
  • Threadの停止を待機する機能の提供
  • 内部実装用のThread管理領域を破棄する機能の提供

このうち、Threadの識別子の取得は大抵のThread Libraryは提供しているはずなので問題はありません。
次のThreadの識別子同士の同値比較は、当初単純に比較すれば良い程度に考えていたのですが、Mac OSでのpthreadのように単純な比較ができないケースもあるため、この要求を追加することにしました。
Threadの停止はmruby側で子スレッドの待ちとかを実装したくなった時のために用意しています。
Thread管理領域の破棄については当然必要なのであまり気にする必要はありません。

mrbgemが一切取り込まれていない状態でもcompileが通ること

これはmrubyのbuildプロセスの問題で、具体的にはmrbc(RubyのコードをC言語のコードに変換するプログラム)のcompileが可能な状態でなければいけないというものです。
これだけだとちょっとよく判らないと思うので、もう少しきちんと説明をします。
まず、mrubyのbuildにはmrbcが必要です。そして、このmrbcはmrubyのcore機能の一部を使ってcompileされます。
このときmrbcのcompile時にはmrbgemは一切取り込まれません。
つまり、Thread APIやLock APIはmrbgemとして提供されますが、その機能が静的にLinkされる構造を前提にしてしまうと、mrbcのcompile時にLink Errorが起きてしまうということになります。
このため、Thread APIやLock APIのための口はmrubyのcore側で静的に用意されているが、
実際のAPIへの呼び出しはmrbgemの実行時に動的にbindされるという仕組みが必要になるわけです。

まとめ

ということで、幾つか課題はあったものの、とりあえずそれらをクリアして動くサンプルを書いてみました。
Threadクラスの実装は、mattnさんのmruby-threadをベースに、今回追加したLock APIやThread APIの実装を追加しています。
とりあえずですが、mruby-thread/example/example.rbを実行すると、

in thread: x is foo
in thread: v is "foo"
in main: v is bar
in main: r is baz

のように表示されるのではないかと思います。
お試しあれ!