使用锁定Dictionary 的键

我有一个Dictionary

编辑:有人向我指出,我的榜样很糟糕。 我的全部意图不是更新循环中的引用,而是根据需要更新/获取数据的不同线程更新不同的值。 我将循环更改为方法。

我需要更新我的字典中的项目 – 一次一个键,我想知道在我的Dictionary对象的.key值上使用锁是否有任何问题?

 private static Dictionary matrixElements = new Dictionary(); //Pseudo-code public static void UpdateValue(string key) { KeyValuePair keyValuePair = matrixElements[key]; lock (keyValuePair.Key) { keyValuePair.Value = SomeMeanMethod(); } } 

这会在法庭上举行还是失败? 我只是希望字典中的每个值都被独立锁定,因此锁定(和更新)一个值不会锁定其他值。 此外,我知道锁定将持续很长时间 – 但数据将无效,直到完全更新。

锁定在代码锁定之外可访问的对象是一个很大的风险。 如果任何其他代码(任何地方)锁定该对象,您可能会遇到一些难以调试的死锁。 还要注意你锁定对象 ,而不是引用,所以如果我给你一个字典,我仍然可以保持对键的引用并锁定它们 – 导致我们锁定同一个对象。

如果你完全封装字典,并自己生成密钥(它们不会被传入,那么你可能是安全的。

但是,请尽量遵守一条规则 – 尽可能将锁定对象的可见性限制为锁定代码本身。

这就是为什么你看到这个:

 public class Something { private readonly object lockObj = new object(); public SomethingReentrant() { lock(lockObj) // Line A { // ... } } } 

而不是看到上面的A行被替换为

  lock(this) 

这样,锁定了单独的对象,并且可见性受到限制。

编辑 Jon Skeet正确地观察到上面的lockObj应该是readonly。

不,这不行。

原因是字符串实习 。 这意味着:

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

都是同一个对象! 因此,你永远不应该锁定字符串,因为如果程序的其他部分(例如同一个对象的另一个实例)也想要锁定同一个字符串,你可能会意外地创建不需要它的锁争用; 甚至可能陷入僵局。

不过,请尽量使用非字符串。 为了最清楚,我总是创建一个单独的锁定对象,这是个人习惯:

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

我建议你这样做。 使用每个矩阵单元格的锁定对象创建一个字典。 然后,在需要时锁定这些对象。

PS。 改变你正在迭代的集合并不是很好。 它甚至会抛出大多数集合类型的exception。 尝试重构这个 – 例如迭代一个键列表,如果它始终是常量,而不是对。

注意:我认为在迭代期间修改集合时exception已经修复

Dictionary不是线程安全的集合,这意味着在没有外部同步的情况下从不同的线程修改和读取集合是不安全的。 对于一个作者多读者场景,Hashtable是(是?)线程安全的,但是Dictionary具有不同的内部数据结构,并且不inheritance此保证。

这意味着当您访问字典以便从其他线程读取或写入时,您无法修改字典,它只能破坏内部数据结构。 锁定密钥不会保护内部数据结构,因为当您修改该密钥时,某人可能正在另一个线程中读取您字典的不同密钥。 即使您可以保证所有密钥都是相同的对象(如关于字符串实习的说法),这也不会让您安全。 例:

  1. 您锁定密钥并开始修改字典
  2. 另一个线程试图获取密钥的值,该密钥恰好落入与锁定的桶相同的桶中。 这不仅在两个对象的哈希码相同时,而且在哈希码%tableSize相同时更频繁。
  3. 两个线程都访问同一个桶(具有相同哈希码%tableSize值的密钥链表)

如果字典中没有这样的键,第一个线程将开始修改列表,第二个线程可能会读取不完整的状态。

如果这样的密钥已经存在,则字典的实现细节仍然可以修改数据结构,例如将最近访问的密钥移动到列表的头部以便更快地检索。 您不能依赖实施细节。

有很多这样的情况,当你有破坏的字典。 因此,您必须拥有外部同步对象(或使用Dictionary本身,如果它未公开),并在整个操作期间锁定它。 如果在操作需要很长时间时需要更细粒度的锁,您可以复制需要更新的密钥,迭代它,在单个密钥更新期间锁定整个字典(不要忘记validation密钥仍在那里)并将其释放到让其他线程运行。

如果我没有弄错,最初的意图是锁定单个元素,而不是锁定整个字典(如数据库中的表级锁与行级锁)

你不能像这里解释的那样锁定字典的密钥。

你可以做的是保留一个与实际字典相对应的锁定对象的内部字典。 因此,当您想要写入YourDictionary [Key1]时,您将首先锁定InternalLocksDictionary [Key1] – 因此只有一个线程会写入YourDictionary。

这里可以找到一个(不太干净)的例子。

刚刚遇到这个并且想到了id,我分享了几年前我写的一些代码,我需要在关键的基础上写一本字典

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

锁类

 class Lock : IDisposable { private static readonly Dictionary Lockedkeys = new Dictionary(); 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; } } 

在你的例子中,你不能做你想做的事!

您将收到System.InvalidOperationException ,并显示Collection已修改的消息; 枚举操作可能无法执行。

这是一个certificate的例子:

 using System.Collections.Generic; using System; public class Test { private Int32 age = 42; static public void Main() { (new Test()).TestMethod(); } public void TestMethod() { Dictionary myDict = new Dictionary(); myDict[age] = age.ToString(); foreach(KeyValuePair 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() 

我可以看到一些潜在的问题:

  1. 字符串可以共享,因此您不一定知道还有谁可能因为其他原因而锁定该键对象
  2. 字符串可能不会被共享:您可能使用值“Key1”锁定一个字符串键,而另一些代码可能具有不同的字符串对象,该字符串对象也包含字符“Key1”。 对于字典,它们是相同的键,但就锁定而言,它们是不同的对象。
  3. 锁定不会阻止更改值对象本身,即matrixElements[someKey].ChangeAllYourContents()