使用锁定Dictionary <string,object =“”> </string,>的键

时间:2008-10-01 13:12:16

标签: c# dictionary locking

我有Dictionary<string, someobject>

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

我需要更新字典中的项目 - 一次一个键,我想知道在使用Dictionary对象的.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();
    }
}

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

7 个答案:

答案 0 :(得分:11)

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

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

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

这就是你看到这个的原因:

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

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

而不是将上面的A行替换为

  lock(this)

这样,一个单独的对象被锁定,可见性受到限制。

编辑 Jon Skeet正确地发现上面的lockObj应该是只读的。

答案 1 :(得分:8)

不,这不行。

原因是string interning。这意味着:

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

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

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

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

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

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

答案 2 :(得分:3)

注意:我在迭代期间修改集合时假设异常已经修复

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

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

  1. 您锁定密钥并开始修改字典
  2. 另一个线程试图获取密钥的值,该密钥恰好与锁定的密钥落入同一个桶中。这不仅在两个对象的哈希码相同时,而且在哈希码%tableSize相同时更频繁。
  3. 两个线程都访问同一个存储桶(具有相同哈希码%tableSize值的密钥链接列表)
  4. 如果字典中没有这样的键,第一个线程将开始修改列表,第二个线程可能会读取不完整的状态。

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

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

答案 3 :(得分:2)

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

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

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

一个(不太干净)的例子可以是found here

答案 4 :(得分:1)

刚刚遇到过这个并认为id分享了几年前我写的一些代码,我需要在关键的基础上使用字典

 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;
        }
    }

答案 5 :(得分: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()

答案 6 :(得分:0)

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

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