Использование блокировки ключа Dictionary<string, object>

StackOverflow https://stackoverflow.com/questions/157511

  •  03-07-2019
  •  | 
  •  

Вопрос

у меня есть Dictionary<string, someobject>.

РЕДАКТИРОВАТЬ:Мне указали, что мой пример плохой.Все мое намерение состояло не в том, чтобы обновлять ссылки в цикле, а в том, чтобы обновлять разные значения, основанные на том, что разные потоки нуждаются в обновлении/получении данных.Я изменил цикл на метод.

Мне нужно обновить элементы в моем словаре - по одному ключу за раз, и мне интересно, есть ли какие-либо проблемы при использовании блокировки значения .key моего объекта Dictionary?

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

Выдержит ли это решение в суде или потерпит неудачу?Я просто хочу, чтобы каждое значение в словаре было заблокировано независимо, чтобы блокировка (и обновление) одного значения не блокировала другие.Также я знаю, что блокировка будет сохраняться в течение длительного времени, но данные будут недействительны до полного обновления.

Это было полезно?

Решение

Блокировка объекта, доступного за пределами блокирующего его кода, представляет собой большой риск.Если какой-либо другой код (где угодно) когда-либо блокирует этот объект, вы можете попасть в тупик, который трудно отладить.Также обратите внимание, что вы блокируете объект, а не ссылку, поэтому, если бы я дал вам словарь, я все равно мог бы хранить ссылки на ключи и блокировать их, что приведет к блокировке одного и того же объекта.

Если вы полностью инкапсулируете словарь и сами генерируете ключи (они никогда не передаются, тогда вы можете быть в безопасности.

Однако старайтесь придерживаться одного правила — по возможности ограничивайте видимость объектов, которые вы блокируете, самим кодом блокировки.

Вот почему вы видите это:

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

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

вместо того, чтобы видеть, что строка A выше заменена на

  lock(this)

Таким образом, отдельный объект блокируется, а видимость ограничивается.

Редактировать Джон Скит правильно заметил, что lockObj выше должен быть доступен только для чтения.

Другие советы

Нет, это не сработает.

Причина в том строковое стажирование.Это значит, что:

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

оба являются одним и тем же объектом!Поэтому никогда не следует блокировать строки, потому что если какая-то другая часть программы (например,другой экземпляр того же объекта) также хочет заблокировать ту же строку, вы можете случайно создать конфликт блокировки там, где в этом нет необходимости;возможно даже тупик.

Однако не стесняйтесь делать это и без строк.Для большей ясности я взял за правило всегда создавать отдельный объект блокировки:

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

Я рекомендую вам сделать то же самое.Создайте словарь с объектами блокировки для каждой ячейки матрицы.Затем при необходимости заблокируйте эти объекты.

ПС.Изменение коллекции, которую вы перебираете, считается не очень приятным занятием.Он даже выдаст исключение для большинства типов коллекций.Попробуйте провести рефакторинг этого - например.перебрать список ключей, если он всегда будет постоянным, а не парами.

Примечание:Я предполагаю, что исключение при изменении коллекции во время итерации уже исправлено.

Словарь не является потокобезопасной коллекцией, что означает, что он нет безопасно изменять и читать коллекцию из разных потоков без внешней синхронизации.Hashtable является (была?) потокобезопасной для сценария «один писатель — много читателей», но словарь имеет другую внутреннюю структуру данных и не наследует эту гарантию.

Это означает, что вы не можете изменять свой словарь во время доступа к нему для чтения или записи из другого потока; это может просто нарушить внутренние структуры данных.Блокировка ключа не защищает внутреннюю структуру данных, поскольку, пока вы изменяете этот самый ключ, кто-то может прочитать другой ключ вашего словаря в другом потоке.Даже если вы можете гарантировать, что все ваши ключи являются одними и теми же объектами (как сказано об интернировании строк), это не обезопасит вас.Пример:

  1. Вы блокируете ключ и начинаете изменять словарь
  2. Другой поток пытается получить значение для ключа, который попадает в ту же корзину, что и заблокированный.Это происходит не только тогда, когда хеш-коды двух объектов одинаковы, но чаще всего, когда хеш-код%tableSize один и тот же.
  3. Оба потока обращаются к одному и тому же сегменту (связанный список ключей с одинаковым значением хэш-кода%tableSize)

Если такого ключа в словаре нет, первый поток начнет изменять список, а второй поток, скорее всего, прочитает неполное состояние.

Если такой ключ уже существует, детали реализации словаря все равно могут изменить структуру данных, например переместить недавно использованные ключи в начало списка для более быстрого поиска.Вы не можете полагаться на детали реализации.

Есть много подобных случаев, когда у вас будет поврежден словарь.Поэтому вам необходимо иметь внешний объект синхронизации (или использовать сам словарь, если он не доступен для общественности) и блокировать его на протяжении всей операции.Если вам нужны более детальные блокировки, когда операция может занять некоторое время, вы можете скопировать ключи, которые необходимо обновить, выполнить итерацию по ним, заблокировать весь словарь во время обновления одного ключа (не забудьте проверить, что ключ все еще там) и отпустить его позвольте другим потокам работать.

Если я не ошибаюсь, первоначальным намерением было заблокировать один элемент, а не блокировать весь словарь (например, блокировка на уровне таблицы или блокировка на уровне таблицы).блокировка уровня строки в БД)

вы не можете заблокировать ключ словаря, как объясняли многие здесь.

Что вы можете сделать, так это сохранить внутренний словарь объектов блокировки, соответствующий реальному словарю.Поэтому, когда вы захотите написать в YourDictionary[Key1], вы сначала заблокируете InternalLocksDictionary[Key1] — так что только один поток будет писать в YourDictionary.

(не слишком чистый) пример может быть нашел здесь.

Просто наткнулся на это и подумал, что могу поделиться кодом, который я написал несколько лет назад, где мне нужен словарь по ключам.

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

В вашем примере вы не можете делать то, что хотите!

Вы получите 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()

Я вижу здесь несколько потенциальных проблем:

  1. строки могут быть общими, поэтому вы не обязательно знаете, кто еще может заблокировать этот ключевой объект по какой еще причине
  2. струны могут нет быть разделенным:вы можете заблокировать один строковый ключ со значением «Key1», а какой-то другой фрагмент кода может иметь другой строковый объект, который также содержит символы «Key1».Для словаря это один и тот же ключ, но с точки зрения блокировки это разные объекты.
  3. Эта блокировка не предотвратит изменения самих объектов значений, т.е. matrixElements[someKey].ChangeAllYourContents()
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top