14

私はTPLと遊んでいて、同じ辞書に並行して読み書きすることでどれくらいの混乱を招くかを調べようとしていました。.Netで辞書を読み書きするときにデッドロックが発生する可能性はありますか?

だから私はこのコードを持っていた:それはかなり確かに台無しにされた

private static void HowCouldARegularDicionaryDeadLock() 
    { 
     for (var i = 0; i < 20000; i++) 
     { 
      TryToReproduceProblem(); 
     } 
    } 

    private static void TryToReproduceProblem() 
    { 
     try 
     { 
      var dictionary = new Dictionary<int, int>(); 
      Enumerable.Range(0, 1000000) 
       .ToList() 
       .AsParallel() 
       .ForAll(n => 
       { 
        if (!dictionary.ContainsKey(n)) 
        { 
         dictionary[n] = n; //write 
        } 
        var readValue = dictionary[n]; //read 
       }); 
     } 
     catch (AggregateException e) 
     { 
      e.Flatten() 
       .InnerExceptions.ToList() 
       .ForEach(i => Console.WriteLine(i.Message)); 
     } 
    } 

を、スローされた例外の多くは、キーが配列の境界の外に、インデックスに関するいくつか存在しませんほとんどについて、ありました。

しかし、しばらくの間、アプリケーションを実行した後、CPUのパーセンテージは25%にとどまり、マシンには8つのコアがあります。 だから私はそれが2つのスレッドは、フル容量で実行されていると仮定します。

enter image description here

その後、私はそれにドットトレースを実行し、そしてこの得た:それは2つのスレッドが100%で実行されている、私の推測と一致する

enter image description here

を。

どちらもDictionaryのFindEntryメソッドを実行しています。

は、その後、私は再びアプリを走った、ドットトレースで、今回は結果が若干異なります。

enter image description here

この時、1つのスレッドFindEntry、他の挿入が実行されています。

私の最初の直感は、それがデッドロックされていたということでしたが、それができないと思っていました。共有リソースは1つしかなく、ロックされていません。

これはどのように説明する必要がありますか?

ps:問題を解決するつもりはありません。ConcurrentDictionaryを使用して修正することも、並列集計を行うこともできます。私はちょうどこれについて合理的な説明を探しています。

+0

Findentryは、エントリを見つけることを推測できます。後で変更されるいくつかのローカル変数を保持するため、ループ終了条件は決して終了しません。これは、別のスレッドによって変更された項目数が変更されないと仮定しているためです。 –

+0

デッドロックではありませんが、内部状態が乱れて無限ループが発生しますか? – CuiPengFei

+0

はい....... – pm100

答えて

8

競合状態(デッドロックではない)のように見えます。これは、あなたがコメントしたように、混乱した内部状態を引き起こします。

ディクショナリはスレッドセーフではないため、別々のスレッドから同じコンテナへの同時読み込みと書き込み(安全性は低くなります)でもありません。

競合状態になると、何が起こるのかは不定になります。この場合、何らかの無限ループのように見えます。

一般に、書き込みアクセスが必要な場合は、何らかの形式の同期が必要です。

16

あなたのコードはDictionary.FindEntryです。 ではなくデッドロック - デッドロックは、2つのスレッドが互いのリソースを解放するのをブロックするときに発生しますが、あなたの場合は2つの一見無限ループが発生します。スレッドはロックされません。

はのはreference sourceにこの方法を見てみましょう:

private int FindEntry(TKey key) { 
    if(key == null) { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); 
    } 

    if (buckets != null) { 
     int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; 
     for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) { 
      if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i; 
     } 
    } 
    return -1; 
} 

forループを見てみましょう。 のインクリメントの部分はi = entries[i].nextで、次のように推測されます。entriesResize methodで更新されるフィールドです。 nextは、内側Entry structの分野です:

public int next;  // Index of next entry, -1 if last 

コードがFindEntryメソッドを終了することができない場合は、最も可能性の高い原因は、あなたが、彼らは無限に作り出すように混乱にエントリを管理しているだろうシーケンスには、nextフィールドが指すインデックスに従っています。Insert methodについては

、それは非常によく似たforループを持っています

​​

Dictionaryクラスは非スレッドセーフであることを文書化されているとして、あなたはとにかく未定義の動作の領域にいます。またはlock昔ながらがうまく問題を解決するようReaderWriterLockSlimとしてConcurrentDictionary又はロッキングパターンを用い

(同時のみ読み取るためDictionaryは、スレッドセーフです)。

+3

他のすべてが失敗した場合は、マニュアルを読んでください。それでも失敗した場合は、ソースコード - >究極のマニュアル – pm100

+0

@Great explanation dude!を読んでください。 (+1) – Christos

関連する問題