1

私は Haskell の同時実行性、特に についていくつか質問してTVarきましたが、TVar.

代わりに、私はこの解決策を提案しました。

(1) プログラム内のすべての共有データを 1 つのデータ構造にラップし、それをIORef. (2) を使用して変更を行うだけですatomicModifyIORef

これにより、デッドロックとライブロックの両方が防止されると思います (一方、TVar は前者のみを防止します)。また、atomicModifyIORef別のサンクをチェーンにリンクするだけなので (これは 2 つのポインター操作です)、これはボトルネックではありません。データに対する実際の操作はすべて、相互に依存しない限り、並行して実行できます。Haskell ランタイム システムがこれを解決します。

ただ、これは単純すぎる気がします。私が見逃した「落とし穴」はありますか?

4

3 に答える 3

7

次の条件が満たされている場合、この設計はおそらく問題ありません。

  • 読み取りは書き込みよりもはるかに一般的です
  • 多数の読み取りが書き込みの間に散在します
  • (おそらく) 書き込みは、グローバル データ構造のごく一部にのみ影響します。

もちろん、これらの条件を考えると、ほぼすべての同時実行システムで問題ありません。あなたはライブロックを懸念しているので、より複雑なアクセス パターンを扱っているのではないかと思います。その場合は、読み進めてください。

あなたのデザインは、次の一連の推論によって導かれているようです。

  1. atomicModifyIORefサンクを作成するだけなので、非常に安価です

  2. 安価なためatomicModifyIORef、スレッドの競合が発生しません

  3. 安価なデータ アクセス + 競合なし = 同時実行性 FTW!

この推論に欠けているステップは次のとおりです。IORef変更はサンクを作成するだけであり、サンクが評価される場所を制御することはできません。データが評価される場所を制御できない場合、真の並列性はありません。

意図したデータ アクセス パターンをまだ提示していないため、これは憶測ですが、データへの変更を繰り返すと、一連のサンクが構築されることが予想されます。その後、ある時点でデータを読み取って評価を強制し、それらのすべてのサンクが 1 つのスレッドで順番に評価されるようにします。この時点で、最初からシングルスレッド コードを作成している可能性があります。

これを回避する方法は、データが IORef に書き込まれる前に (少なくとも希望する範囲で) 評価されるようにすることです。これが の戻りパラメータのatomicModifyIORef目的です。

変更することを意図したこれらの関数を検討してくださいaVar :: IORef [Int]

doubleList1 :: [Int] -> ([Int],())
doubleList1 xs = (map (*2) xs, ())

doubleList2 :: [Int] -> ([Int], [Int])
doubleList2 xs = let ys = map (*2) xs in (ys,ys)

doubleList3 :: [Int] -> ([Int], Int)
doubleList3 xs = let ys = map (*2) xs in (ys, sum ys)

これらの関数を引数として使用すると、次のようになります。

  1. !() <- atomicModifyIORef aVar doubleList1- サンクのみが作成され、データは評価されません。次からどのスレッドを読んでも、不愉快な驚きですaVar!

  2. !oList <- atomicModifyIORef aVar doubleList2(:)- 新しいリストは、初期コンストラクター、つまりorを決定する場合にのみ評価され[]ます。まだ実際の作業は行われていません。

  3. !oSum <- atomicModifyIORef aVar doubleList3- リストの合計を評価することにより、計算が完全に評価されることが保証されます。

最初の 2 つのケースでは、ほとんど作業が行われていないため、atomicModifyIORefはすぐに終了します。 しかし、その作業はそのスレッドでは行われておらず、いつ行われるかはわかりません。

3 番目のケースでは、目的のスレッドで作業が行われたことがわかります。最初にサンクが作成され、IORef が更新されます。次に、スレッドが合計の評価を開始し、最終的に結果を返します。しかし、合計の計算中に他のスレッドがデータを読み取るとします。サンク自体の評価を開始する可能性があり、2 つのスレッドが重複した作業を行っていることになります。

一言で言えば、このデザインは何も解決していません。同時実行性の問題が難しくない状況では機能する可能性がありますが、検討しているような極端なケースでは、複数のスレッドが重複した作業を行ってサイクルを焼き尽くします。また、STM とは異なり、いつ、どのように再試行するかを制御することはできません。少なくとも STM では、トランザクションの途中でアボートすることができます。

于 2012-04-12T10:11:58.320 に答える
5

まあ、それはうまく構成されないでしょう。また、単一のIORefを介してすべての共有メモリの変更をシリアル化すると、一度に1つのスレッドのみが共有メモリを変更できるようになります。実際に行ったのはグローバルロックだけです。はい、動作しますが、速度が遅く、TVarやMVarほど柔軟ではありません。

于 2012-04-12T00:19:17.563 に答える
1

AFAICT 計算がIORef内容を処理した後に未評価のサンクを残す場合、そのサンクは、必要に応じて並列に評価されるのではなく、結果を使用しようとするスレッドで単純に評価されます。ここMVarで、ドキュメントの落とし穴セクションを参照してください

あなたが解決しようとしている具体的な問題 (または単純化された類似の問題) を提供すると、他の人にとってより興味深く、役立つかもしれません。

于 2012-04-12T02:11:49.267 に答える