Следует ли использовать шаблон двойной проверки блокировки с ReaderWriteLockSlim? - PullRequest
3 голосов
/ 04 августа 2011

Нужен ли шаблон двойной контроль при использовании ReaderWriterLockSlim?

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

  1. сначала получает блокировку чтения, затем проверяет существование
  2. затем ввод обновляемой блокировки чтения и повторная проверка ,
  3. затем ввести блокировку записи, если элемент все еще отсутствует в словаре?

Примерно так:

void populateIfNotPresent( object thing )
{
        _lock.EnterReadLock( ) ;

        bool there = _dictionary.ContainsKey(thing);

        _lock.ExitReadLock( ) ;

        // Remember, the specs say nothing can be removed from this dictionary.
        if (!there)
        {
            _lock.EnterUpgradeableReadLock( ) ;

            try
            {
                if( !_dictionary.ContainsKey( thing ) )
                {
                    _lock.EnterWriteLock( ) ;
                    try
                    {
                        populate( thing ) ;
                    }
                    finally
                    {
                        _lock.ExitWriteLock( ) ;
                    }
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock( ) ;
            }
        }
}

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

Что ты думаешь? Это перебор?

Ответы [ 2 ]

4 голосов
/ 04 августа 2011

Класс ReaderWriterLockSlim (как и любая другая блокировка чтения-записи) предназначен для большого числа операций чтения по сравнению с числом операций записи.

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

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

Однако, если это only место, где вы выполняете чтение / запись (илибольшинство происходит здесь) тогда возникает проблема, ваше отношение чтения / записи недостаточно высоко, чтобы гарантировать блокировку чтения / записи, и вам следует обратиться к другому методу синхронизации.

Тем не менее, в конце концовэто все о тестировании производительности;если вы собираетесь оптимизировать реализацию, вам нужно измерить ее текущую производительность, чтобы сравнить ее, в противном случае это просто преждевременная оптимизация .

1 голос
/ 05 августа 2011

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

Сколько стоит? Что ж, если вы ожидаете увидеть много пропусков кэша, то может иметь смысл выполнить обновляемую блокировку; однако, если вы не ожидаете увидеть много пропусков кэша, значит, вы делаете ненужную блокировку. В общем, я бы выбрал самое простое решение, которое выполнит работу. Оптимизация замков, как правило, не там, где вы получите максимальную отдачу от вашего доллара, в первую очередь ищите более крупные вещи для оптимизации.

Предложение:
Что-то, что может дать вам гораздо больше отдачи, - это Striped Dictionary ( StripedMap в Java - неплохое начало, и его не должно быть очень сложно понять).

Основная идея StripedMap / StripedDictionary заключается в том, что у вас есть массив блокировок:

object[] syncs = new object[n]();
// also create n new objects

Вы должны чередовать свою карту с достаточно большим количеством полос, чтобы разрешить количество потоков, которые вы должны ввести в метод без столкновений. У меня нет никаких данных для резервного копирования, но предположим, что вы ожидаете, что до 8 потоков войдут в карту, тогда вы, вероятно, могли бы использовать 8 или более блокировок (полос), чтобы гарантировать, что все 8 потоков могут войти в карта одновременно. Если вы хотите лучше «застраховаться» от «столкновений», то создайте больше полос, скажем, 32 или 64.

Когда вы вводите метод populateIfNotPresent, вы блокируете одну из этих блокировок в зависимости от хеш-кода:

void populateIfNotPresent( object thing )
{
    lock(syncs[thing.GetHashCode()%syncs.Length])
    {
        if(!dictionary.ContainsKey(thing))
        {
            populate(thing);
        }
    }
}

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

Вы уже ожидаете, что populateIfNotPresent будет дорогим ЕСЛИ элемент отсутствует , но если у вас есть чередующийся словарь, то вы можете иметь несколько потоков, работающих в разных секторах словаря, не сталкиваясь с каждым Другой. Это даст вам гораздо большую выгоду, чем избавление от нескольких циклов ЦП от проверки, существует ли объект, потому что дорогая операция - это когда объект существует .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...