Использование блокировки ключа словаря <строка, объект> - PullRequest
8 голосов
/ 01 октября 2008

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

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

Ответы [ 7 ]

11 голосов
/ 01 октября 2008

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

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

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

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

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

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

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

  lock(this)

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

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

8 голосов
/ 01 октября 2008

Нет, это не будет работать.

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

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

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

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

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

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

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

3 голосов
/ 01 октября 2008

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

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

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

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

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

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

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

2 голосов
/ 11 февраля 2009

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

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

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

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

1 голос
/ 14 апреля 2016

Только что натолкнулся на это и подумал, что 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;
        }
    }
0 голосов
/ 01 октября 2008

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

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

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

Вы получите 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...