8

本体内で集中的な操作を実行する Parallel.ForEach ループがあります。

この操作では、Hashtable を使用して値を格納し、他の連続するループ項目に再利用できます。集中的な操作が完了した後に Hashtable に追加すると、集中的な操作を再度実行する代わりに、次のループ項目が Hashtable を検索してオブジェクトを再利用できます。

ただし、私は Parallel.ForEach を使用しているため、Hashtable.Add と ContainsKey(key) 呼び出しが並行して実行されている可能性があるため、非同期になるという安全でない問題があります。ロックを導入すると、パフォーマンスの問題が発生する可能性があります。

サンプルコードは次のとおりです。

Hashtable myTable = new Hashtable;
Parallel.ForEach(items, (item, loopState) =>
{
    // If exists in myTable use it, else add to hashtable
    if(myTable.ContainsKey(item.Key))
    {
       myObj = myTable[item.Key];
    }
    else
    {
       myObj = SomeIntensiveOperation();
       myTable.Add(item.Key, myObj); // Issue is here : breaks with exc during runtime
    }
    // Do something with myObj
    // some code here
}

このシナリオを処理できる API、TPL ライブラリ内のプロパティ設定が必要です。ある?

4

4 に答える 4

18

あなたが探してSystem.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>いる。新しい並行コレクションは、大幅に改善されたロックメカニズムを使用しており、並列アルゴリズムで優れたパフォーマンスを発揮するはずです。

編集:結果は次のようになります。

ConcurrentDictionary<T,K> cache = ...;
Parallel.ForEach(items, (item, loopState) =>
{
    K value;
    if (!cache.TryGetValue(item.Key, out value))
    {
        value = SomeIntensiveOperation();
        cache.TryAdd(item.Key, value);
    }

    // Do something with value
} );

警告の言葉:の要素がすべて一意でitemsはない場合、そのキーに対して2回呼び出される可能性があります。この例では、キーはに渡されませんが、「値で何かを行う」コードがキー/値Aとキー/値Bのペアを実行でき、1つの結果のみがキャッシュに保存されることを意味します(必ずしも最初の結果である必要はありません)。 SomeIntensiveOperationによって計算されたもの)。問題がある場合は、これを処理するための並列レイジーファクトリが必要になります。また、明らかな理由から、SomeIntensiveOperationはスレッドセーフである必要があります。item.KeySomeIntensiveOperationSomeIntensiveOperation

于 2009-11-01T18:41:57.060 に答える
4

System.Collections.Concurrent名前空間を確認してください ConcurrentDictionaryが必要だと思います

于 2009-11-01T18:42:56.107 に答える
3

ReaderWriterLockを使用します。これは、短時間の読み取りが多く、書き込みが少ない作業で優れたパフォーマンスを発揮します。あなたの問題はこの仕様に適合しているようです。

すべての読み取り操作は迅速に実行され、ロックフリーになります。誰かがブロックされるのは書き込みが行われているときだけであり、その書き込みはハッシュテーブルに何かを押し込むのにかかる時間だけです。

MSDNのReaderWriterLockSlim

私はいくつかのコードを捨てると思います...

ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
Hashtable myTable = new Hashtable();
Parallel.ForEach(items, (item, loopState) =>
{
    cacheLock.EnterReadLock();
    MyObject myObj = myTable.TryGet(item.Key);
    cacheLock.ExitReadLock();

    // If the object isn't cached, calculate it and cache it
    if(myObj == null)
    {
       myObj = SomeIntensiveOperation();
       cacheLock.EnterWriteLock();
       try
       {
           myTable.Add(item.Key, myObj);
       }
       finally
       {
           cacheLock.ExitWriteLock();
       }           
    }
    // Do something with myObj
    // some code here
}

static object TryGet(this Hashtable table, object key)
{
    if(table.Contains(key))
        return table[key]
    else
        return null;
}
于 2009-11-01T18:42:08.607 に答える
1

(多かれ少なかれ明示的な)ロックを使用する以外に正しい選択肢はありません(同期されたハッシュテーブルは、すべてのメソッドをロックでオーバーライドするだけです)。

別のオプションとして、ディクショナリが非同期になるようにすることもできます。競合状態によって辞書が破損することはありません。コードで余分な計算を行う必要があるだけです。コードをプロファイリングして、ロックまたはメモ化の欠落が悪い影響を与えるかどうかを確認します。

于 2009-11-01T18:33:21.990 に答える