代码之家  ›  专栏  ›  技术社区  ›  Per Hornshøj-Schierbeck

在字典的键上使用lock<string,object>

  •  8
  • Per Hornshøj-Schierbeck  · 技术社区  · 16 年前

    我有一个 Dictionary<string, someobject> .

    编辑:有人向我指出,我的例子不好。我的全部意图不是更新循环中的引用,而是根据不同的线程更新不同的值,需要更新/获取数据。我把循环改成了一种方法。

    我需要更新字典中的项目-一次只更新一个键,我想知道使用字典对象的.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 回复  |  直到 8 年前
        1
  •  11
  •   Community    7 年前

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

    如果 您完全封装了字典,并自己生成密钥(它们从来没有被传入,那么您可能是安全的)。

    但是,尽量遵循一个规则——尽可能限制锁定代码本身的对象的可见性。

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

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

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

      lock(this)
    

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

    编辑 Jon Skeet 正确地观察到上面的lockobj应该是只读的。

        2
  •  8
  •   Sander    16 年前

    不,这样不行。

    原因是 string interning . 这意味着:

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

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

    尽管如此,您可以使用非字符串来完成这项工作。为了清晰起见,我习惯于创建单独的锁对象:

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

    我建议你也这么做。为每个矩阵单元创建一个带有锁对象的字典。然后,在需要时锁定这些对象。

    另外,更改您正在迭代的集合被认为不是很好。它甚至会引发大多数集合类型的异常。尝试重构它——例如,迭代键列表,如果它总是常量,而不是成对的。

        3
  •  3
  •   Ilya Ryzhenkov    16 年前

    注意:在迭代过程中修改集合时,我假定已经修复了异常

    字典不是线程安全集合,这意味着它是 在没有外部同步的情况下,可以安全地从不同的线程修改和读取集合。哈希表是?线程安全,适用于一个编写器多个读卡器场景,但字典具有不同的内部数据结构,并且不继承此保证。

    这意味着您不能在从另一个线程访问字典进行读或写时修改字典,它只会破坏内部数据结构。锁定密钥并不能保护内部数据结构,因为当您修改这个密钥时,可能有人在另一个线程中读取字典的不同密钥。即使你能保证你所有的钥匙都是一样的东西(比如说绳子被扣住),这也不能保证你的安全。例子:

    1. 你锁住钥匙,开始修改字典
    2. 另一个线程试图获取密钥的值,该值恰好落在与锁定的bucket相同的bucket中。这不仅在两个对象的hashcode相同时,而且在hashcode%tablesize相同时更频繁。
    3. 两个线程都在访问同一个bucket(具有相同hashcode%tablesize值的键的链接列表)

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

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

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

        4
  •  2
  •   Rush Frisby    9 年前

    如果我没有弄错,最初的目的是锁定一个元素,而不是锁定整个字典(比如表级锁和数据库中的行级锁)。

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

    您可以做的是保存一个锁对象的内部字典,它与实际的字典相对应。所以当你想写你的字典[key1]时,你首先会锁定internalLocksDictionary[key1]——所以只有一个线程会写你的字典。

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

        5
  •  1
  •   Mark    8 年前

    刚刚遇到这个问题,我想我可以分享几年前我写的一些代码,在那里我需要一本关键的字典。

     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;
            }
        }
    
        6
  •  0
  •   Ray Hayes    16 年前

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

    你会得到一个 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()
    
        7
  •  0
  •   abatishchev Karl Johan    12 年前

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

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