辞書のキーのロックを使用< string、object>
-
03-07-2019 - |
質問
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つの値をロック(および更新)しても他の値がロックされないように、辞書の各値を独立してロックするだけです。また、ロックが長時間保持されることは承知していますが、データは完全に更新されるまで無効になります。
解決
コードをロックする外部からアクセスできるオブジェクトをロックすることは、大きなリスクです。他のコード(どこでも)がそのオブジェクトをロックすると、デバッグが難しいデッドロックが発生する可能性があります。また、参照ではなくオブジェクトをロックすることに注意してください。したがって、辞書を提供した場合、キーへの参照を保持してそれらをロックする可能性があります。 / p>
ディクショナリを完全にカプセル化し、キーを自分で生成した場合(これらは渡されないため、安全である可能性があります。
ただし、1つのルールに固執するようにしてください。ロックするオブジェクトの可視性を可能な限りロックコード自体に制限してください。
だからあなたはこれを見ます:
public class Something
{
private readonly object lockObj = new object();
public SomethingReentrant()
{
lock(lockObj) // Line A
{
// ...
}
}
}
上の行Aが置き換えられるのを見るのではなく
lock(this)
これにより、別のオブジェクトがロックされ、可視性が制限されます。
編集 Jon Skeet は、上記のlockObjを読み取り専用にする必要があることを正しく認識しました。
他のヒント
いいえ、これは機能しません。
理由は、文字列インターンです。つまり:
string a = "Something";
string b = "Something";
両方が同じオブジェクトです!したがって、プログラムの別の部分(たとえば、この同じオブジェクトの別のインスタンス)が同じ文字列をロックしたい場合、必要のないロック競合を誤って作成する可能性があるため、文字列をロックしないでください。デッドロックの可能性もあります。
ただし、文字列以外でも自由に実行できます。明確にするために、常に個別のロックオブジェクトを作成することを個人的な習慣にします。
class Something
{
bool threadSafeBool = true;
object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool
}
同じことをお勧めします。すべてのマトリックスセルのロックオブジェクトで辞書を作成します。次に、必要に応じてこれらのオブジェクトをロックします。
PS。繰り返し処理するコレクションを変更することは、あまり良いこととは言えません。ほとんどのコレクションタイプでも例外をスローします。これをリファクタリングしてみてください-例えばペアではなく常に一定である場合、キーのリストを反復処理します。
注:反復中にコレクションを変更する際の例外はすでに修正されていると想定しています
辞書はスレッドセーフなコレクションではありません。つまり、外部の同期なしで異なるスレッドからコレクションを変更および読み取ることは安全ではありません。 Hashtableは、1人のライターが複数のリーダーを使用するシナリオでは(以前は)スレッドセーフですが、Dictionaryは内部データ構造が異なり、この保証を継承しません。
これは、他のスレッドから読み取りまたは書き込みのために辞書にアクセスしている間は辞書を変更できないことを意味し、内部データ構造を破壊するだけです。キーをロックしても内部データ構造は保護されません。そのキーを変更している間、誰かが別のスレッドで辞書の異なるキーを読み取っている可能性があるためです。すべてのキーが同じオブジェクトであることを保証できたとしても(文字列のインターンについて述べたように)、これは安全な側面をもたらしません。例:
- キーをロックし、辞書の変更を開始します
- 別のスレッドが、ロックされているバケットと同じバケットにたまたまあるキーの値を取得しようとします。これは、2つのオブジェクトのハッシュコードが同じ場合だけでなく、hashcode%tableSizeが同じ場合により頻繁に起こります。
- 両方のスレッドが同じバケット(同じhashcode%tableSize値を持つキーのリンクリスト)にアクセスしています
辞書にそのようなキーがない場合、最初のスレッドはリストの変更を開始し、2番目のスレッドは不完全な状態を読み取る可能性があります。
そのようなキーが既に存在する場合、辞書の実装の詳細によりデータ構造が変更される可能性があります。たとえば、最近アクセスしたキーをリストの先頭に移動して検索を高速化します。実装の詳細に依存することはできません。
辞書が破損すると、そのような多くのケースがあります。そのため、外部同期オブジェクト(またはパブリックに公開されていない場合は辞書自体を使用)を持ち、操作全体でロックする必要があります。操作に時間がかかる場合、よりきめ細かいロックが必要な場合は、更新する必要があるキーをコピーし、反復処理し、単一キーの更新中に辞書全体をロックします(キーがまだ存在することを確認することを忘れないでください)他のスレッドを実行させます。
間違っていなければ、元の意図は、辞書全体をロックするのではなく、単一の要素をロックすることでした(DBのテーブルレベルロックと行レベルロックなど)
ここで説明したように、辞書のキーをロックすることはできません。
できることは、実際の辞書に対応するロックオブジェクトの内部辞書を保持することです。したがって、YourDictionary [Key1]に書き込みたい場合は、まずInternalLocksDictionary [Key1]をロックします。したがって、YourDictionaryに書き込むスレッドは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;
}
}
あなたの例では、やりたいことはできません!
Collection was modifiedのメッセージとともに 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()
そこにはいくつかの潜在的な問題があります:
- 文字列は共有できるため、他の理由で他の誰がそのキーオブジェクトをロックしているのかを必ずしも知る必要はありません
- 文字列は共有されない可能性があります。 :「Key1」という値を持つ1つの文字列キーをロックできます。他の一部のコードには、文字「Key1」も含む別の文字列オブジェクトが含まれる場合があります。辞書にとっては、それらは同じキーですが、ロックに関する限り、それらは異なるオブジェクトです。
- そのロックは、値オブジェクト自体への変更を妨げません。つまり、
matrixElements [someKey] .ChangeAllYourContents()