
by chooyutshing under CC BY-NC-SA
さて、クリスマス・イブになりました。みなさまいかがお過ごしでしょうか?昨日のソニック・ジェネレーションズのイベントで、ハードロックアレンジのクリスマスソングを聞くまではクリスマスイブのことなどすっかり忘れていました。
今日はまさかの三週目のPyPy アドベントカレンダーのエントリーを書いています。
RPythonでスレッドを使ってみる
Pythonとほぼ互換のPyPyでは当然のスレッドをサポートしています。PyPyはRPythonで書かれていてtranslate.pyでビルドされています。つまり、RPythonで作る実行ファイル用のスレッドサポートがpypyのコードのどこかにあるはずです。ためしにgrep -r "thread" *としてみると・・・いろいろわんさか引っかかりますね。その中でも、pypy/module/thread/以下のモジュールがあやしそうです。
pypy/module以下のフォルダは、RPythonで処理されたり初期化されたりして、最終的にPyPyの組み込みライブラリとして使われるモジュール群になります。thread以外にも、bz2やzipなどのファイルアーカイブ関連とか、_multiprocessing、pyexpatなどもこの中にあります。
オブジェクトスペース
さて、この中のモジュールをそのままコピーすればいいのかというとそうではありません。@jbkingさんのアドベントカレンダーのエントリーで説明されている通り、RPythonにはオブジェクトスペースという概念があります。言語の基本的な論理構造をインタフェースとして切り出したものです。こちらにオブジェクトスペースで提供しているインタフェースの数々があります。
たとえば、is_trueを書き換えると、Rubyみたいに0を真として扱うような言語が作れますし、演算子を書き換えると、PerlやAWKのように、適当に文字列表現の数値を変換してから計算するような言語が作れるといった寸法です。callを書き換えて遅延評価やmemoizeを実装することも可能かもしれません。
pypy/module/以下のライブラリは、このオブジェクトスペースが整備された、言語処理系のためのライブラリとして実装されています。pypy/module/thread/__init__.pyを見ると、pypy/module/thread/os_thread.pyからstart_new_thread関数をインポートしていますが、こちらのコードを見ると、最初の引数にspaceとあります。
我々にはオブジェクトスペースなど不要!
ですが、RPythonを言語を作るためではなく、マゾっ子志向プログラミング言語(MOPL)として、あくまでも、ふつうのプログラムを作るための処理系として使う場合には、オブジェクトスペースを1から作るのは大げさです。そのため、このPyPy向けのモジュールを参考にしながら、実装していく必要があります。
逆に言語を作りたい人は、このあたりから勉強していくといいのかもしれません。基本的には、パーサを作って、オブジェクトスペースを操作するための命令列を作る、というのがRPython Toolchainを使った言語作りです(僕の理解では)。
さて、os_thread.pyを見ると、ll_thread.pyの関数を呼び出しています。ざっとこの2つのモジュールをコードリーディングしてみると、つぎのようなことが分かりました。
- ll_thread.start_new_thread()を使えばスレッドが作れる。
- ll_thread.start_new_thread()にはパラメータを渡せない・・・
- os_threadでは、グローバル変数?を使ってパラメータ渡しをエミュレートしている。
- os_threadではパラメータ渡しでトラブルが置きないように、スレッド起動時にロックをかけている。
- スレッドの最初ではll_thread.gc_thread_prepare()を呼ぶ必要がある
このように、コードリーディングした結果を手で実装してみて、確認していきます。
スレッドをためしてみた
できあがったのが上記のコードです。グローバル変数の読み書きのタイミングよりは、list.pop()の方がちょっとは安全かな?(要出展)ということで、スレッド分パラメータをあらかじめ作っておいてpop()していますが、まぁこれは実験コードですので、本番コードを作る場合にはPyPyの実装と同じく、ロックを使う実装にすべきです。スレッドごとに指定された時間分スリープしながら(重い仕事をしているイメージ)、終わったら、自分のIDをコンソールに出力しています。translateで実行ファイルを作ってみて、実行するとつぎのように表示されます。
$ ./testthread-c finish task:1 finish task:2 finish task:1 finish task:3 finish task:1 finish task:4 finish task:2 finish task:1 finish task:5 finish task:1 finish task:3 finish task:2 finish task:1 finish task:1 finish task:4 finish task:2 finish task:1 finish task:3 finish task:1 finish task:5
どうやらきちんと実行できているようですね。ちなみに、translate.pyには--threadオプションがあるのですが、付けても付けなくても、普通に動きました。
まとめ
スレッドを題材に使いましたが、説明したかったのはつぎのようなことです。
- RPythonでできることを調べるなら、PyPyのコードが参考になる
- 言語を作らないなら、オブジェクトスペースに依存した部分を取り払って使う必要がある
それでは最後になりました。つぎは@drillbitさん?















