Gauche:control.jobメモ
Shiro: 0.9.2のcontrol.job (GaucheRefj:control.job) にrace condition が見つかったんだけど、どうfixすれば良いかというのを考えてたら そもそもcontrol.jobをなぜ作ったかって当初の目的をだいぶ忘れてることに気づいたんで、 思いだしがてらメモしておく。
control.jobは単独で何か役に立つモジュールではない。ただ、control.* として いくつか考えてたライブラリがあって、それぞれに共通して使える構造があるなあと 思ってそれをくくりだしておいたもの。
- スレッドプール。control.thread-pool として実装。
- プロセスプール。いくつかプロセスを立ち上げといてジョブを振って計算させる。 複数マシンによる分散処理も考慮。
- タスクネットワーク。タスク間の依存関係を定義しておいて、計算できるものから 計算させる。バックエンドには単一スレッドによる実行もスレッドプールかプロセスプール による並行実行も選べる。
さらに、アプリケーションによってジョブの管理方法が違ってくる。
- 「やりっぱなし」モデル - 生産者がジョブをキューにどんどん突っ込んで、消費者が それを実行してくけど、生産者は個々のジョブの結果を気にしない (仕事の成果は ジョブ構造体を使わずに消費者がどっかに通知する)。
- 双方向キューモデル - 生産者はジョブをキューに突っ込む。消費者はジョブを取って 仕事して、結果をジョブに格納して終了ジョブキューに突っ込む。生産者もしくは第三者は終了ジョブキューを 順次読み出して処理。
- 実行終了待ち合わせモデル - 個々のjobをjob-waitできる。
そんでもって、複雑な待ち合わせモデルはいらないからとにかく速く実行したい、っていうアプリのために、必要無い場合はmutexでの待ち合わせとかデータ構造のアロケートとかはしなくても良いようにしたい。job構造体のアロケートでさえ、必要ならプリアロケートしておいて、実行中はノーアロケーションでいけるようにもしたい。
ってなことを考えてた。
やりっぱなしモデルや双方向キューモデルで、ジョブ実行中の干渉を許さないとすれば、 job-statusとjob-resultを書き込むのはジョブを実行しているスレッドだけで、 それが読まれるのはジョブ実行が終了した後なので、書き込みにmutexは必要ない。
job-mark-killed!みたいに途中で別のスレッドが干渉するとか、job-waitで別の スレッドが状態をチェックしにくるとかいう時だけ、mutexが必要になる。
そこをjob作成時にきちんと宣言させて、それによって場合分けをちゃんとやらんといかん。
少し整理して、スレッドとの干渉についてjobに3つのフレーバーがある、 というふうに定義することにした。
- waitable - 他のスレッドがこのjobの状態変化を監視して待つことができる。 mutexとCV必須。waitableなjobは自動的にcancellable。
- cancellable - jobを実行してるスレッドとは別のスレッドが、jobの状態を上書きできる。 (正確には、実行をキャンセルするわけではないのでこの名称はちょいと悩んだのだけれど、 overridableとかpreembtibleだとかちょっと長い単語しか思いつかなかった)。 状態変更時にmutexによる排他制御必要。 上書き時に、waitしてるスレッドがあれば起こすが、それ以外のアクションはjobを使う 上位レイヤ依存 (例:control.thread-poolではプールのshutdown時にjobをkillとマークしてから 終了キューに入れる。)
- 無印 - 特に保護なし。状態の変更はjobを走らせるスレッドのみが行い、 それを他スレッドが読み出せるようになった時にはもう状態は確定して変化しない、 ということを上位レイヤが保証する。