8

私は持っていDictionary<string, someobject>ます。

編集:私の例が悪いと指摘されました。私の全体的な意図は、ループ内の参照を更新することではなく、データを更新/取得する必要があるさまざまなスレッドに基づいてさまざまな値を更新することでした。ループをメソッドに変更しました。

ディクショナリ内の項目を更新する必要があります - 一度に 1 つのキーで、ディクショナリ オブジェクトの .key 値のロックを使用する際に問題があるかどうか疑問に思っていましたか?

private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>();

//Pseudo-code

public static void UpdateValue(string key)
{
    KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key];
    lock (keyValuePair.Key)
    {
        keyValuePair.Value  = SomeMeanMethod();
    }
}

それは法廷で持ちこたえるか、それとも失敗しますか? ディクショナリ内の各値を個別にロックして、1 つの値をロック (および更新) しても他の値がロックされないようにしたいだけです。また、ロックが長時間保持されることは承知していますが、完全に更新されるまでデータは無効になります。

4

7 に答える 7

11

コードロックの外部からアクセス可能なオブジェクトをロックすることは大きなリスクです。他のコード(どこでも)がそのオブジェクトをロックした場合、デバッグが難しいデッドロックが発生する可能性があります。また、参照ではなくオブジェクトをロックすることに注意してください。辞書を提供した場合でも、キーへの参照を保持してロックする可能性があります。これにより、同じオブジェクトがロックされます。

辞書を完全にカプセル化し、自分でキーを生成する場合(キーが渡されることはないので、安全な場合があります。

ただし、1つのルールに固執するようにしてください。可能な限り、ロックするオブジェクトの可視性をロックコード自体に制限してください。

それがあなたがこれを見る理由です:

public class Something
{
  private readonly object lockObj = new object();

  public SomethingReentrant()
  {
    lock(lockObj)    // Line A
    {
      // ...
     }
   }
}

上記の行Aがに置き換えられるのを見るのではなく

  lock(this)

そうすると、別のオブジェクトがロックされ、可視性が制限されます。

Edit Jon Skeetは、上記のlockObjが読み取り専用であるべきであることを正しく観察しました。

于 2008-10-01T13:23:17.357 に答える
10

いいえ、これは機能しません。

その理由は文字列のインターンです。この意味は:

string a = "Something";
string b = "Something";

両方とも同じオブジェクトです!したがって、プログラムの他の部分(たとえば、この同じオブジェクトの別のインスタンス)も同じ文字列をロックしたい場合、文字列を必要としない場所で誤ってロック競合を作成する可能性があるため、文字列をロックしないでください。おそらくデッドロックですら。

ただし、文字列以外の場合は、これを自由に行ってください。わかりやすくするために、私は常に個別のロックオブジェクトを作成することを個人的な習慣にしています。

class Something
{
    bool threadSafeBool = true;
    object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool
}

同じことをお勧めします。すべての行列セルのロックオブジェクトを使用して辞書を作成します。次に、必要に応じてこれらのオブジェクトをロックします。

PS。繰り返し処理しているコレクションを変更することは、あまり良いこととは見なされません。ほとんどのコレクションタイプで例外をスローすることさえあります。これをリファクタリングしてみてください。たとえば、ペアではなく常に一定である場合は、キーのリストを繰り返し処理します。

于 2008-10-01T13:18:31.470 に答える
3

注:反復中にコレクションを変更する場合の例外はすでに修正されていると想定しています

ディクショナリはスレッドセーフなコレクションではありません。つまり、外部同期なしで異なるスレッドからコレクションを変更および読み取ることは安全ではありません。Hashtable は、1 人のライターが複数のリーダーを使用するシナリオでは (だった?) スレッドセーフですが、Dictionary は異なる内部データ構造を持ち、この保証を継承しません。

これは、他のスレッドから読み取りまたは書き込みのために辞書にアクセスしている間は辞書を変更できないことを意味し、内部データ構造を壊す可能性があります。キーをロックしても内部データ構造は保護されません。そのキーを変更している間、誰かが別のスレッドで辞書の別のキーを読み取っている可能性があるためです。すべてのキーが同じオブジェクトであることを保証できたとしても(文字列のインターンについて述べたように)、これは安全な側にはなりません。例:

  1. キーをロックし、辞書の変更を開始します
  2. 別のスレッドが、ロックされたものと同じバケットにたまたま入るキーの値を取得しようとします。これは、2 つのオブジェクトのハッシュコードが同じ場合だけでなく、hashcode%tableSize が同じ場合により頻繁に発生します。
  3. 両方のスレッドが同じバケットにアクセスしています (同じ hashcode%tableSize 値を持つキーのリンクされたリスト)

辞書にそのようなキーがない場合、最初のスレッドがリストの変更を開始し、2 番目のスレッドが不完全な状態を読み取る可能性があります。

そのようなキーが既に存在する場合、ディクショナリの実装の詳細は、データ構造を変更する可能性があります。たとえば、最近アクセスしたキーをリストの先頭に移動して、取得を高速化します。実装の詳細に依存することはできません。

そのような場合、辞書が壊れている場合がよくあります。そのため、外部同期オブジェクトを用意し (または公開されていない場合は Dictionary 自体を使用し)、操作全体でそれをロックする必要があります。操作に時間がかかる可能性がある場合に、より細かいロックが必要な場合は、更新する必要があるキーをコピーし、それを繰り返し、単一のキー更新中に辞書全体をロックし (キーがまだそこにあることを確認することを忘れないでください)、解放することができます。他のスレッドを実行させます。

于 2008-10-01T16:02:13.090 に答える
2

私が間違っていなければ、元の意図は辞書全体をロックするのではなく、単一の要素をロックすることでした (DB のテーブルレベルのロックと行レベルのロックのように)

ここで説明したように、辞書のキーをロックすることはできません。

あなたができることは、実際の辞書に対応するロックオブジェクトの内部辞書を保持することです。したがって、YourDictionary[Key1] に書き込みたい場合は、最初に InternalLocksDictionary[Key1] をロックします。つまり、1 つのスレッドだけが YourDictionary に書き込みます。

(あまりきれいではない)例はここにあります。

于 2009-02-11T07:45:43.987 に答える
1

これに出くわしたばかりで、数年前に書いたコードを共有して、キーベースで辞書が必要だったと思いました

 using (var lockObject = new Lock(hashedCacheID))
 {
    var lockedKey = lockObject.GetLock();
    //now do something with the dictionary
 }

ロッククラス

class Lock : IDisposable
    {
        private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>();

        private static readonly object CritialLock = new object();

        private readonly string _key;
        private bool _isLocked;

        public Lock(string key)
        {
            _key = key;

            lock (CritialLock)
            {
                //if the dictionary doesnt contain the key add it
                if (!Lockedkeys.ContainsKey(key))
                {
                    Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references
                }
            }
        }

        public string GetLock()
        {
            var key = Lockedkeys[_key];

            if (!_isLocked)
            {
                Monitor.Enter(key);
            }
            _isLocked = true;

            return key;
        }

        public void Dispose()
        {
            var key = Lockedkeys[_key];

            if (_isLocked)
            {
                Monitor.Exit(key);
            }
            _isLocked = false;
        }
    }
于 2016-04-14T11:11:27.520 に答える
0

あなたの例では、やりたいことはできません!

コレクションが変更されたというメッセージを含むSystem.InvalidOperationExceptionが発生します。列挙操作が実行されない場合があります。

証明する例を次に示します。

using System.Collections.Generic;
using System;

public class Test
{
    private Int32 age = 42;

    static public void Main()
    {
       (new Test()).TestMethod();
    }

    public void TestMethod()
    {
        Dictionary<Int32, string> myDict = new Dictionary<Int32, string>();

        myDict[age] = age.ToString();

        foreach(KeyValuePair<Int32, string> pair in myDict)
        {
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            ++age;
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            myDict[pair.Key] = "new";
            Console.WriteLine("Changed!");
        }
    }   
}

出力は次のようになります。

42 : 42
42 : 42

Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
   at Test.TestMethod()
   at Test.Main()
于 2008-10-01T13:24:58.037 に答える
0

潜在的な問題がいくつか見られます。

  1. 文字列は共有できるため、他の理由でそのキーオブジェクトをロックしている可能性のある他の人を必ずしも知ることはできません
  2. 文字列が共有されていない可能性があります: 値 "Key1" を持つ 1 つの文字列キーをロックしている可能性があり、別のコードの一部が文字 "Key1" を含む別の文字列オブジェクトを持っている可能性があります。辞書にとっては同じキーですが、ロックに関する限り、それらは異なるオブジェクトです。
  3. そのロックは、値オブジェクト自体への変更を妨げません。つまり、matrixElements[someKey].ChangeAllYourContents()
于 2008-10-01T13:31:14.153 に答える