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

У меня 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();
    }
}

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

Не ответ, но вы знаете, что если у вас уже есть пара, вы можете использовать pair.Value вместо повторного вызова оператора [] в Словаре?

OregonGhost 01.10.2008 17:16

На самом деле я пробовал, но значение доступно только для чтения, или так мне сказала моя IDE?

Per Hornshøj-Schierbeck 01.10.2008 17:21

вы не можете изменять элементы foreach

sieben 01.10.2008 17:26

Где именно параллелизм, которого вы хотите избежать? Если вы запустите свой цикл в несколько потоков, первый блокирует первую клавишу, все остальные блокируются при нажатии первой клавиши. Итак, я не понимаю сценарий, который вы хотите решить. Не могли бы вы прояснить свой вопрос по этому поводу?

TToni 01.10.2008 17:47

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

Per Hornshøj-Schierbeck 01.10.2008 22:09

Вот почему я попросил прояснить вопрос :-) Идет много дискуссий, которые сосредотачиваются на том, почему цикл не работает, что вы аннулируете любой перечислитель коллекции, обновляя его, и так далее. Так что да, плохой пример, пожалуйста, выберите лучший.

TToni 01.10.2008 23:48

Аналогичный вопрос: stackoverflow.com/questions/168249/…

Constantin 05.10.2008 21:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
7
17 835
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

Причина - интернирование струн. Это означает, что:

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

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

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

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

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

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

Вы также можете отключить интернирование строк - я считаю, что это делают все сборки .NET framework.

Jeff Yates 01.10.2008 17:26

«Ключом» к словарю может быть любой объект, а не только строка! Это не применимо, если бы ключ был целым числом!

Ray Hayes 01.10.2008 17:28

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

Jason Jackson 01.10.2008 17:48
Ответ принят как подходящий

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

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

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

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

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

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

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

  lock(this)

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

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

Я бы добавил, что lockObj также почти всегда следует объявлять только для чтения.

Jon Skeet 01.10.2008 17:44

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

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

Я не хочу блокировать весь словарь - только значение keypairvalue - поэтому никакие два процесса не будут обновлять одно и то же значение в keypairvalue за раз.

Per Hornshøj-Schierbeck 01.10.2008 17:26

Я не меняю ключ - только значение, так что проблем быть не должно.

Per Hornshøj-Schierbeck 01.10.2008 22:09

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

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

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

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

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

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

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