Hatena::ブログ(Diary)

あどけない話

2011-09-08

(続)Haskell(GHC)での軽量ユーザスレッドの実装方法

Haskell(GHC)での軽量ユーザスレッドの実装方法で、Cmm が軽量スレッドのポイントと書きました。しかし、GHC の実装者 Simon Marlow 先生から「Cmm は関係ないよ」と教えて頂きました。

StgCall

StgCall がいくつかのレジスタを保存するのは、採用しているCの関数呼び出し規約がそうなっているから。スレッドとはまったく無関係。

GHCランタイムは C で書かれている。よって、スケジューラからスレッドを呼び出すと、C から Haskell を呼び出すことになる。C では、呼び出された関数レジスタを保存しなければならない。Haskell関数には、こういった制約はない。なので、C から Haskell関数を呼び出す際は、C の規約を肩代わりしてやる必要がある。それが StgCall。

スタック

Haskell のスタックは C のスタックと同様、特定のレジスタが指す場所。(Cの場合はスタックレジスタだろうが、Haskell の場合はそうではない。しかし本質的には同じこと。)

ただし、Haskell のスタックは、自由に領域を拡張したり、動かしたりできる。GC は、スタックを正しく扱えるように作られている。だから、スレッドのスタックは小さい領域で始められる。

コンテキストスイッチ

安全なポイントのみでコンテキストスイッチする。(コードを見る限り、タイムアウトやスタック溢れのなどが対象。スタック溢れの場合は、スタックを拡張してキューに入れる。これは安全そう。しかし、タイムアップの場合は安全なのか僕には判断できない。)

よって、保存しなければならないコンテキストは、CPU に依存しない。だから、真のプリエンプティブだとは言えないが、十分に頻繁なのでプログラマーには認識できない。

外部言語の関数呼び出し

ユーザスレッドの実装は、外部言語の関数呼び出しをサポートする際に行き詰まることが多い。つまり、呼び出した外部言語の関数でブロックされ、他のユーザスレッドが動かなくなる。

Haskell では、外部言語の関数呼び出し専用のカーネルスレッドを用意していて、ユーザスレッドがブロックされないようになっている。この手法は素晴らしいが、他の言語コミュニティは、素晴らしさに気付いていないようだ。

あわせて読みたい

sumiisumii 2011/09/12 10:48 いつも面白い記事どうもです。

> StgCall がレジスタを保存するのは、Cの関数呼び出し規約がそうなっているから。

> C では、呼び出された関数がレジスタを保存しなければならない。

念のため、ここだけ読むとCのABIではすべてのレジスタがcallee saveであるかのようにも聞こえますが、これはABIやレジスタによりますよね。Simon Marlowさんのメール(http://www.haskell.org/pipermail/glasgow-haskell-users/2011-September/020882.html)にも"certain registers"とあります。あまり本題に関係のない基本的前提ですみません。

kazu-yamamotokazu-yamamoto 2011/09/12 10:55 あー、お気づきになりましたか。乱暴な表現だとは知っていたんですが、横着しました。後で直します。

s50s50 2011/09/18 05:22 最近好奇心からこのあたりの実装を調べてる非Haskellユーザです。

FFI用の専用カーネルスレッドというのはforkOSのbound threadのことでしょうか?
bound thread導入の動機はOpenGLや再帰ロックのような、FFIの挙動が特定のカーネルスレッドのステートに依存する場合に必要、ということでしたよね。
foreign importがブロックするかどうかとも関係してるのでしょうか。

それっぽいのはExtending the Haskell Foreign Function Interface with Concurrencyにある"I/O Service Thread"かなと思ったのですが、これはunbound threadらしいので、誤記でなければ特別のカーネルスレッドを要しないものに見えます(少なくとも今のI/O Manager ThreadはforkIOで作られるunbound thread)。

ブロックに関してはノンブロッキングモードのFDだかepoll/kqueueあたりとsafeなforeign importを使って解決してるのかなー、となんとなく思っているのですが、今一つ理解しきれていません。ぶっちゃけHaskellが読めないので大部分RTSのCコードやドキュメントからの推測です!

-threadedのM:Nスレッドで頑張る攻めの姿勢はかなり好きなので、Haskell使ってないのに仕組みが気になっています。

s50s50 2011/09/19 14:00 自己解決しました!
safe foreign callは呼び出し前にRTSのsuspendThread呼び出しが自動挿入され、-threadedでsuspendThreadは余ってるカーネルスレッド(ワーカー)が無い場合にreleaseCapability_の中で新たなカーネルスレッドを作るので、Haskellスレッドの全体がブロックすることは無い、という実装のようでした。
お騒がせしました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/kazu-yamamoto/20110908/1315457683