2012-04-11 6 views
39

最近、TVarに関するいくつかの質問をしましたが、私はまだライブロックについて懸念しています。デッドロックやリソース枯渇のない並行した汎用データ構造

だから私はこのような構造を考えた:

  1. 各トランザクションは、(おそらく作成順に割り当てられた)一意のプライオリティを取得します。
  2. トランザクションは、アクセスするデータに対して読み取り/書き込みロックを取得しようとします。当然のことながら、同時読み取りは問題ありませんが、1つの書き込みロックは他のすべてのものを除外します(読み取りと書き込みの両方)。
  3. トランザクションAはトランザクションBよりも高い優先度を持ちます。Aがロックを保持している場合はBが待機しますが、Bがロックを保持し、Aがそれを望む場合、Bはロックから起動され、Aは取得し、 TVar)。 Bは再試行のために現在の優先順位を保持します。
  4. ロックが解放され、待機中のトランザクションがある場合、最も優先度の高いトランザクションに進み、残りのトランザクションは待機し続けます。

このシステムでは、デッドロックを防ぎますが、飢餓を防ぐと考えています(TVarとは異なります)。誰かがそのようなシステムを実装しているのかどうかは不思議に思っていました。

もちろん、このようなアプローチを簡単に拡張して、ユーザーが優先順位を指定できるようにすることができます。

優先度は(user_supplied_prio, auto_increment)となり、user_supplied_prioが優先されますが、優先度の高いタスクはFIFO順で解決されます。

コメント/ソリューション:

私はそれについて考えるときに実際に、私はすでに記述すると、単純にすべてのデータの周りに包まれた1 IORefを使用して、とだけatomicModifyIORefを使用することにより、Haskellで存在します。 atomicModifyIORefは、トランザクションが確実に順番に適用されるようにします。しかしながら、これは、データ構造が連続的である(すなわち、効果的に1つのスレッドに限定される)が、怠惰のために実際には平行であることを意味すると考えることができる。

これを説明するには、高価な関数fを考えてみましょう。これをData.Mapにキー "foo"のデータに適用します。

これは(foo -> x)の代わりに(foo -> future(f x))を置き換えたものです。このスレッドは実際には(f x)の内容を解決し続けますが、その間に "bar"にgを適用できます。 gを "bar"に適用することは "foo"の結果を必要としないので、これを同時に処理することができます。

デッドロックがなく、飢えもなく、最終的にすべてのトランザクションが処理されます(受け取った順番で処理されます)。

+2

私はHaskellに存在するそのようなシステムを知らない。ほとんどのユーザーにとってはあまりにも複雑すぎるように見えますが、実装するのはむしろ面倒です。特に、割り当てられたロックを無効にするには、ポーリング(やや面倒なコード)または非同期例外(他のワームの可能性があります)が必要です。私はあなたの実装にSTMを試して、それがどのように動作するかを見てみることをお勧めします。 STMアルゴリズムは、通常、それを交換する必要がある場合は時間がかかりません。 –

+3

STMの観点から言えば、ランタイムに優先度機構を追加すると(日付に基づいて述べたものに似ています)、私が信じるライブロックの問題は解決します。ただし、スケジューラの選択肢が大幅に制限される可能性があります。これは、現在のSTMランタイムにこのようなメカニズムがない理由でもあります。 PS:質問が解決したかどうかをコミュニティに知らせるために、以前の質問に対する回答の一部を「受け入れられた回答」としたい場合があります。 – Peter

+1

パフォーマンス要件は何ですか?グローバルチケットロック(http://en.wikipedia.org/wiki/Ticket_lock)などの十分に粗い手段で、または単一のスレッドによって連続して実行されるすべてのアクションをエンキューすることで、必要なものを得ることができます。洗練された同期方法は、より高いオーバーヘッドを持つ傾向があります。グローバル同期が特定のワークロードのボトルネックでない場合は、おそらく高速です。 – Heatsink

答えて

1

すべての要求を決定的に処理するようにワーカースレッドを設定することができます。したがって、誰も飢えてしまうことはありません。この戦略は、合理的に効率的で、ライブロックに耐性があります。

-- yes, this is a horrible name 
createManagerFactory :: a -> IO ((IO a), IO (((a -> a) -> IO a))) 

IO aは、読み取り専用のSTMアクションで値を安全かつ迅速に照会するアクションです。 (a - > a)は値を変更する純粋な関数なので、((a - > a) - > IO a)は修飾子関数を取り、安全に値を変更して新しい値を返すアクションです。

これを1回実行して工場を初期化します。

(query, modifierFactory) <- createManagerVactory initValue 

次に、スレッドごとにこれを実行して新しい修飾子を生成します。

myModify <- modifierFactory 

createManagerFactoryには、次の操作を行います。

  • はinitValueを含むTVarを作成します(valueTVarそれを呼び出します)。
  • TVarの空のコレクション含むTVar作成 -
  • リターンA 'クエリ' 結果として、(どちらか( > a))は(modifyTVarCollectionそれを呼び出す)
  • リターン(アトミック$ readTVar valueTVarを) - (左Aにそれを初期化し、(> A)(いずれか)(modifyTVarそれを呼び出す)

    • 新しいTVarを作成します。modifyTVarCollection

    についてmodifierFactoryがこれを行うだろう知っているmodifierFactory )をvalueTVarの現在の値と比較して、それをmodifyTVarCollectionに追加します

  • modifyTVarが1つのSTMアクションでロードされ、次に別のSTMアクションがmodifyTVarがロードされるまで(右a(左a)の結果値を返し、その値を返します。

ワーカースレッドがこのループを実行します:

  • アクション1で、modifyTVarCollectionからすべてのクエリTVarsをつかむ、とその値を確認してください。それらがすべて(左a)の値を含んでいる場合は、再試行します(modifier関数でmodifyTVarをロードした他のスレッドやmodifierFactoryが新しいmodifierTVarを作成してコレクションに追加するまでブロックします)。右(a - > a)を含むすべてのmodifyTVarsのリストを返します
  • 返されたすべてのmodifyTVarsを繰り返します。それぞれのTVarに対して、修正関数を読み込み、修正を安全に実行し、結果をmodifyTVarに返すアクションを実行します(左a)
関連する問題