6

forkIO を使用してスレッドを作成する場合、実行して識別子 (threadID) を取得する関数を提供する必要があります。その後、ワークロードや MVAR などを介してこの動物と通信できます。ただし、私の理解では、作成されたスレッドは非常に制限されており、スレッド作成のために提供された機能が命令である SIMD 方式でのみ機能します。スレッドが開始されたときに提供した機能を変更できません。これらのユーザー スレッドは、最終的に OS スレッドにマップされた OS によるものであることを理解しています。

Haskell スレッドと OS スレッドがどのようにやり取りするのか知りたいです。まったく異なることを行う Haskell スレッドが、同じ OS スレッドにマップされるのはなぜですか? 固定命令で OS スレッドを開始する必要がなかったのはなぜですか? (forkIO で必要なため) スケジューラ(?) は、分散される可能性のあるアプリケーション内のユーザー スレッドをどのように認識しますか? 言い換えれば、なぜ OS スレッドはこれほどまでに柔軟なのでしょうか?

最後に、アプリケーション内から選択したスレッドのヒープをダンプする方法はありますか?

4

2 に答える 2

12

まず、1 つの簡単な誤解に対処しましょう。

これらのユーザー スレッドは、最終的には OS スレッドにマップされた OS によるものであることを理解しています。

実際には、Haskell ランタイムは、そのプールから特定の OS スレッドが実行する Haskell スレッドを選択する役割を担っています。

次に、質問を 1 つずつ行います。

まったく異なることを行う Haskell スレッドが、同じ OS スレッドにマップされるのはなぜですか?

現時点では FFI を無視して、すべての OS スレッドは実際には Haskell ランタイムを実行しています。これは、準備ができている Haskell スレッドのリストを追跡します。ランタイムは、実行する Haskell スレッドを選択し、コードにジャンプして、スレッドが制御をランタイムに戻すまで実行します。その時点で、ランタイムは同じスレッドを実行し続けるか、別のスレッドを選択する可能性があります。

要するに、多くの Haskell スレッドを 1 つの OS スレッドにマップすることができます。実際には、OS スレッドはただ 1 つのこと、つまり Haskell ランタイムを実行しているだけだからです。

固定命令で OS スレッドを開始する必要がなかったのはなぜですか? (forkIO で必要なため)

私はこの質問を理解していません (そして、それは 2 番目の誤解から生じていると思います)。Haskell スレッドを固定命令で開始するのとまったく同じ意味で、固定命令で OS スレッドを開始します。それぞれに対して、実行するコードのチャンクを与えるだけで、それが実行されます。

スケジューラ(?) は、分散される可能性のあるアプリケーション内のユーザー スレッドをどのように認識しますか?

「分散」は危険な言葉です。通常、複数のマシンにコードを分散させることを指します (おそらく、ここで意図したものとは異なります)。複数のスレッドがある場合に Haskell ランタイムがどのように判断できるかについては、簡単ですforkIO

言い換えれば、なぜ OS スレッドはこれほどまでに柔軟なのでしょうか?

OS スレッドが Haskell スレッドよりも柔軟であるということは私には明らかではないので、この質問は少し奇妙です。

最後に、選択したスレッドのヒープをアプリケーション内からダンプする方法はありますか?

マルチスレッドアプリケーションなどで、Haskellヒープをダンプするためのツールについては、実際にはまったく知りません。必要に応じて、 vacuumなどのパッケージを使用して、特定のオブジェクトから到達可能なヒープの部分の表現をダンプできます。過去に大きな成功を収めたこれらのダンプを視覚化するためにvacuum-cairoを使用しました。

詳細については、私のイントロからマルチスレッド gtk2hs プログラミングまでの中間の 2 つのセクション、「規約」と「外部インポート」をお楽しみください。また、おそらく「非スレッド ランタイム」のセクションの一部もご覧ください。

于 2012-09-12T18:51:29.000 に答える
9

あなたの質問に直接答えようとする代わりに、マルチスレッド化された Haskell プログラムがどのように実装されるかの概念モデルを提供しようとします。多くの詳細と複雑さは無視します。

オペレーティング システムは、ハードウェア割り込みを使用してプリエンプティブ マルチスレッドを実装し、計算の複数の「スレッド」を同じコアで同時に論理的に実行できるようにします。

オペレーティング システムによって提供されるスレッドは、重量が重い傾向があります。これらは、特定のタイプの「マルチスレッド」アプリケーションによく適しており、Linux などのシステムでは、複数のプログラムを同時に実行できる基本的に同じツールです (優れたタスク)。

ただし、これらのスレッドは、Haskell などの高水準言語での多くの用途には少し重いです。基本的に、GHC ランタイムはミニ OS として機能し、OS スレッドの上に独自の「スレッド」を実装します。これは、OS がコアの上にスレッドを実装するのと同じ方法です。

Haskell のような言語がこのように実装されることは、概念的に容易に想像できます。Haskell の評価は、「サンクの強制」で構成されます。ここで、サンクは、1. 別の値 (サンク) に依存したり、2. 新しいサンクを作成したりする可能性のある計算単位です。

したがって、それぞれが同時にサンクを評価する複数のスレッドを想像することができます。評価されるサンクのキューを構築します。各スレッドは、キューの先頭をポップし、そのサンクが完了するまで評価してから、キューから新しいサンクを選択します。操作parとその同類は、そのキューにサンクを追加することで、新しい計算を「開始」できます。

このモデルを IO アクションに拡張することも、想像に難くありません。それぞれが単純に純粋なサンクを強制する代わりに、Haskell 計算の単位がいくらか複雑になると想像します。このようなランタイムの疑似 Haskell:

type Spark = (ThreadId,Action)
data Action = Compute Thunk | Perform IOAction

注: これは概念を理解するためだけのものです。物事がこのように実装されているとは思わないでください。

Spark を実行すると、そのスレッド ID に「スロー」された例外を探します。何もないと仮定すると、実行は、サンクを強制するか、IO アクションを実行することで構成されます。

明らかに、ここでの私の説明は非常に手の込んだものであり、複雑さを無視しています。詳細については、GHC チームは、Marlow らによる「Runtime Support for Multicore Haskell」などの優れた記事を書いています。また、オペレーティング システムのテキスト ブックも参照することをお勧めします。スケジューラの作成方法について詳しく説明されていることがよくあります。

于 2012-09-12T19:34:11.697 に答える