powered by simpleCommunicator - 2.0.53     © 2025 Programmizd 02
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Блокировка по ключу
39 сообщений из 39, показаны все 2 страниц
Блокировка по ключу
    #39359124
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
здравствуйте!

помогите пожалуйста разобраться с блокировками по строковому ключу

нашёл такую статью http://johnculviner.com/achieving-named-lock-locker-functionality-in-c-4-0/
там описывается несколько решений, самое быстрое это NamedReaderWriterLocker,
но дело не только в скорости. меня смущает, что независимо от выбранного способа
постоянно забивается память, либо интернированными строками,
либо объектами блокировок. мне это совершенно не подходит.
мне нравится использование ReaderWriterLockSlim, но не нравится, что он оседает
в памяти, даже если больше не нужен и возможно даже не понадобится.

можно как-то обеспечить существование блокировки в памяти, только пока есть хотя
бы один читатель/писатель? нужны 100% гарантии, что писать может только один поток.

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

спасибо!
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359188
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheможно как-то обеспечить существование блокировки в памяти, только пока есть хотя
бы один читатель/писатель? нужны 100% гарантии, что писать может только один поток.
Читать/писать куда? Может просто это "куда" блокировать?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359221
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

читать значит не производить никаких действий, изменяющих данные
писать значит добавлять, обновлять, удалять, и не только в БД
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359224
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

блокировка БД не подходит, так как изменяющее действие это в совокупности
много разных действий не только в БД
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359304
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheмне нравится использование ReaderWriterLockSlim, но не нравится, что он оседает
в памяти, даже если больше не нужен и возможно даже не понадобится.
Там есть RemoveLock(), вопрос только когда его вызывать? Можно например счетчик установленных блокировок отслеживать (CurrentReadCount, WaitingReadCount, WaitingUpgradeCount, WaitingWriteCount), когда в ноль уйдет, ставить блокировку на запись и удалять. Как вариант делать это периодически из другого потока, например раз в секунду прошел по всему _lockDict и удалил неиспользуемые.

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

ИМХУ сомневаюсь что есть готовые решения. Контроль ненужности сильно утяжеляет работу.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359343
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

такое решение и напрашивается периодически очищать кеш блокировок,
но оно мне не нравится, так как возникает угроза удаления в момент
необходимости захвата блокировки, значит это тоже должно быть обёрнуто
в какую-то блокировку со счётчиком блокировок ровно такая же проблема,
но ближе к тому что нужно

разве нет стандартного и универсального решения для блокировки
по ключу? ConcurrentDictionary же работает без интернирования.

может в MemoryCache объекты блокировки закидывать?
не будет слишком уродливым решением?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359351
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheразве нет стандартного и универсального решения для блокировки
по ключу? ConcurrentDictionary же работает без интернирования.
ConcurrentDictionary просто подменяет хранилище интернов. Потому и работает. Но суть не меняется.

Суть в том что надо где-то хранить соответствие строки с ключем и объекта блокировки. В примерах с интернированием сама строка выступает этим объектом, т.к. после интернирования одинаковые строки используют один и тот же объект String.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359418
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheможет в MemoryCache объекты блокировки закидывать?
не будет слишком уродливым решением?
Неуправляемое решение. Может лишнее долго лежать, а может наоборот удалить используемое если кэш маловат.

Можно тут счетчик добавить и вытесняющий кэш реализовать
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
class MyReaderWriterLockSlim : ReaderWriterLockSlim {
   public UInt64 last_access;

   MyReaderWriterLockSlim(UInt64 now) {
      last_access = now;
   }
}

public class NamedReaderWriterLocker
{
	private readonly ConcurrentDictionary<string, MyReaderWriterLockSlim> _lockDict = new ConcurrentDictionary<string, MyReaderWriterLockSlim >();
	private volatile UInt64 now_count = 0;

	public ReaderWriterLockSlim GetLock(string name)
	{
		now_count++;
		var l = _lockDict.GetOrAdd(name, s => new MyReaderWriterLockSlim(now_count));
		l.last_access = now_count;
		return l;
	}
...


На скорость работы эта добавка не должна сильно повлиять.

При очистке просто удалять сколько-то с наименьшим last_access, получится тот же вытесняющий кэш. Сколько удалять можно например так вычислять: найти минимальный заблокированный и удалить половину меньше его.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359461
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Такая еще мысль появилась про удаление.
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
void DeleteOld() {
  var to_del = ConcurrentDictionary<string, ReaderWriterLockSlim>; // промежуточное хранилище для удаляемых
  перебираем _lockDict {
    if(ReaderWriterLockSlim не имеет блокировок) {
        блокируем его на запись
        добавляем в to_del
        удаляем из _lockDict
  }
  пауза 50-100 мс чтобы встали на ожидание те кто успел получить ссылку на удаляемый объект
  перебираем to_del {
      if(есть ожидающие блокировки ReaderWriterLockSlim) {
        блокируем на запись новый ReaderWriterLockSlim из _lockDict
        освобождаем старый из to_del
        даем доработать ожидающим старого
      }
  }
  to_del вместе используемыми им объектами почистит сборщик мусора
}


Таким образом удаление будет гарантировать что одновременно несколько потоков не получат доступ к одному ключу.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359488
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Еще мысль про кэш 19960299 .

Удалять неиспользованные с момента прошлой очистки. И добавить минимально допустимый размер, ниже которого не опускаться.
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
public class NamedReaderWriterLocker {
...
private volatile UInt64 last_clean = 0;

void DeleteOld() {
    ...
    перебираем _lockDict {
      if(ReaderWriterLockSlim.last_access < last_clean and не имеет блокировок) {
         ...
      }
      if(_lockDict.Lenght < допустимого размера) break;
    }
    ...
    last_clean = now_count;
}
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359498
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima TConcurrentDictionary просто подменяет хранилище интернов. Потому и работает. Но суть не меняется.

посмотрел исходники, они используют свою таблицу блокировок по хешу
блокировкой является обычный new object()

Dima TНеуправляемое решение. Может лишнее долго лежать, а может наоборот удалить используемое если кэш маловат.

значит решение не просто уродливое, а неправильное (


Dima TМожно тут счетчик добавить и вытесняющий кэш реализовать

а тут не нужно использовать volatile или Interlocked.Exchange
чтобы не получить в разных потоках разные значения?


Dima TТакая еще мысль появилась про удаление
Dima TТаким образом удаление будет гарантировать что одновременно несколько потоков не получат доступ к одному ключу.

я вижу тут проблему, что объекты в to_del будут дорабатывать когда
появится новый клиент, а для него блокировки уже в _lockDict не найдётся
получится что запись не будет полностью заблокирована

идея мне нравится! вытесняющий кеш это то что нужно. только как
обеспечить гарантии во время удаления, что выбранные блокировки
для удаления никто не подхватит? interlocked exchange?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359518
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheа тут не нужно использовать volatile или Interlocked.Exchange
чтобы не получить в разных потоках разные значения?
volatile я добавил для счетчика, можно Interlocked.Exchange, но ИМХУ это лишнее. Если два объекта будут иметь одинаковый last_access то ничего страшного не случится. Это просто альтернатива метке времени. Можешь сделать DateTime last_access и писать туда DateTime.Now, а при удалении обрабатывать с last_access < DateTime.Now - 5 сек. Возможно так даже быстрее будет, т.к. volatile тоже небольшое замедление дает.

absintheя вижу тут проблему, что объекты в to_del будут дорабатывать когда
появится новый клиент, а для него блокировки уже в _lockDict не найдётся
получится что запись не будет полностью заблокирована

идея мне нравится! вытесняющий кеш это то что нужно. только как
обеспечить гарантии во время удаления, что выбранные блокировки
для удаления никто не подхватит? interlocked exchange?
Мой пример внимательнее смотри, там уже решена эта проблема. Перед удалением из _lockDict объект блокируется на запись, поэтому если даже кто-то успеет его получить, то просто повиснет в ожидании пока эта блокировка не снимется.
to_del нужен именно для того чтобы таких подвисших вычислить и дать им корректно доработать. Смотри пример внимательно.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359616
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima Tvolatile я добавил для счетчика, можно Interlocked.Exchange, но ИМХУ это лишнее

извиняюсь, не заметил, действительно есть volatile


Dima TМой пример внимательнее смотри, там уже решена эта проблема. Перед удалением из _lockDict объект блокируется на запись, поэтому если даже кто-то успеет его получить, то просто повиснет в ожидании пока эта блокировка не снимется

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

тут точно не будет дедлока или косяка? этот ход конём смотрится
подозрительно, но удивительно красиво )
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359619
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

попробую реализовать логику по твоим примерам и погоняю на тестах
спасибо за помощь!
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359648
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheтут точно не будет дедлока или косяка? этот ход конём смотрится
подозрительно, но удивительно красиво )
Дедлока не будет. В худшем случае попавшие на удаляемый ReaderWriterLockSlim подвиснут на ту самую паузу 50-100 мс, которая нужна для их ожидания, т.к. есть "зазор" между GetLock() и Enter***Lock() во время которого ссылка на объект уже получена, а блокировка еще не установлена. Вероятность маленькая что в зазор кто-то попадет, но есть, поэтому прежде чем это оптимизировать - добавь запись в лог о возникновении таких ситуаций. Будет много - есть куда оптимизировать.

Решение не идеальное (как все решения с паузами), т.к. есть минимальная вероятность что при сильной перегрузке проца кто-то после паузы выйдет из зазора. Т.е. не успеет за время паузы это сделать. Как вариант в течении секунды каждые 50 мс проходить по to_del с проверкой.

PS Если будешь писать по моим инструкциям - выкладывай сюда. Проверю и может еще кому пригодится.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359653
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Еще после всех проверок в to_del на последок не помешает вызвать ReaderWriterLockSlim.Dispose() тогда даже если где-то кто-то, попав в зазор, вызовет Enter***Lock(), то на вызове получит исключение ObjectDisposedException (так MSDN обещает), это лучше чем испортить данные.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359702
Фотография Cat2
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
absinthe,

Просто интересно, при решении какой задачи возникла данная проблема? Вдруг у меня похожее, а я ее не замечаю
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359881
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

возник ещё один вопрос
можно использовать WeakReference?
и периодически удалять ссылки с IsAlive == false?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359884
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Cat2Просто интересно, при решении какой задачи возникла данная проблема? Вдруг у меня похожее, а я ее не замечаю

есть ресурсы договоры и поставки
операция с договором должна блокировать все операции со связанными поставками
операция с поставкой должна блокировать все операции с договором
связанных объектов на самом деле больше, поэтому блокировка общая
и не связана с одной единственной записью БД, поэтому стратегии
блокировки таблиц БД здесь не подойдут, ещё и другие есть операции
не связанные с БД, загрузка и обработка файлов, отправка сообщений, сигналы
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359885
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Cat2,

можно было бы обойтись блокировкой по интернированному ключу
или с помощью словаря ключей, как в ссылке которую я скинул
но так как ключей получается много, меня печалит что память
со временем забивается этими ключами, их действительно много
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359909
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheDima T,

возник ещё один вопрос
можно использовать WeakReference?
и периодически удалять ссылки с IsAlive == false?
Замена ReaderWriterLockSlim на WeakReference и проверка IsAlive == false перед удалением никак не решает проблем с многопоточностью. Проблемы все те же самые: первый поток проверил, второй создал объект, первый удалил.
Вобщем без разницы что проверять ReaderWriterLockSlim не имеет блокировок или IsAlive == false.
Этим только код усложнишь и лишнюю память займешь под объекты WeakReference.

Основная память у тебя на строки (ключи) расходуется. На WeakReference ты их никак не заменишь.

Как вариант: для экономии памяти можно в качестве ключа использовать не строку, а ее хэш. Это Int32 всего 4 байта.
Код: c#
1.
2.
3.
4.
5.
	private readonly ... _lockDict = new ConcurrentDictionary<Int32, ReaderWriterLockSlim >();

	public ReaderWriterLockSlim GetLock(string name)	{
		retun _lockDict.GetOrAdd(name.GetHashCode(), s => new ReaderWriterLockSlim());
	}


В твоем случае никакой проблемы не будет если хэши двух разных ключей совпадут, в этом случае два разных ключа воспользуются одной общей блокировкой, т.е. в худшем случае вместо того чтобы обрабатываться параллельно они будут ожидать друг друга. Но это очень маловероятно, т.к. хэш функция подбирается так чтобы как можно реже давать одинаковый результат на разных строках.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359914
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Еще мысль: если тебе пиковая производительность не критична, т.е. не смертельно что несколько разных несвязанных обработок будут синхронизироваться одним ReaderWriterLockSlim, то можно хэш уменьшить до нужного размера, что уменьшит максимальное количество элементов.
Код: c#
1.
		return _lockDict.GetOrAdd(name.GetHashCode() & 0xFFFF, s => new ReaderWriterLockSlim());


Тут ты гарантированно не будешь иметь _lockDict более 65536 элементов. Займет он максимум 1-2 Мб памяти. Можно вообще не заморачиваться на удаление.

PS Тут я не уверен что ключ будет разнообразен, это надо потестить на твоих строках, возможно надо что-то другое, например
Код: c#
1.
2.
Int32 hash = name.GetHashCode();
return _lockDict.GetOrAdd((hash & 0xFFFF) ^ (hash >> 16), ...
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359962
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima TЗамена ReaderWriterLockSlim на WeakReference и проверка IsAlive == false перед удалением никак не решает проблем с многопоточностью. Проблемы все те же самые: первый поток проверил, второй создал объект, первый удалил.
Вобщем без разницы что проверять ReaderWriterLockSlim не имеет блокировок или IsAlive == false.
Этим только код усложнишь и лишнюю память займешь под объекты WeakReference.

я предполагал замену MyReaderWriterLockSlim на WeakRefrence<ReaderWriterLockSlim>
тогда коллекция to_del будет не нужна

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
private ConcurrentDictionary<string, WeakReference<ReaderWriterLockSlim>> _lockDic;

public ReaderWriterLockSlim GetLock(string key)
{
   var lockRef = _lockDic.AddOrUpdate(key, 
      (k) => new WeakReference<ReaderWriterLockSlim>(new ReaderWriterLockSlim()), 
      (k, v) => v.Target == null
         ? new WeakReference<ReaderWriterLockSlim>(new ReaderWriterLockSlim())
         : v
      );
   return lockRef.Target;
}


...

// вызывается периодически
private void Clear()
{
   foreach(var item in _lockDic.Where(p => !p.Value.IsAlive).ToList())
   {
       WeakReference<ReaderWriterLockSlim> value;
       _lockDic.TryRemove(item.Key, out value);
   }
}



Dima TЕще мысль: если тебе пиковая производительность не критична, т.е. не смертельно что несколько разных несвязанных обработок будут синхронизироваться одним ReaderWriterLockSlim, то можно хэш уменьшить до нужного размера, что уменьшит максимальное количество элементов.

я очень крепко думал на эту тему, когда смотрел на реализацию
ConcurrentDictionary

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

как всетаки оценить какое решение лучше? я начал писать проект
для тестирования, но возникли трудности создания условий
много потоков должны получать блокировку и нужно обеспечить,
чтобы где-то 10-20% потоков пытались заблокировать один ресурс
одновременно, не получается

правильно ли я представил решение с WeakReference? правильно ли
его использовать таким образом?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359964
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
absinthe,

может так будет надёжней и быстрее:

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
private ConcurrentDictionary<string, WeakReference<ReaderWriterLockSlim>> _lockDic;

public ReaderWriterLockSlim GetLock(string key)
{
   WeakReference<ReaderWriterLockSlim> lockRef;
   ReaderWriterLockSlim lockObj;
   if(!_lockDic.TryGet(key, out lockRef) || (lockObj = lockRef.Target) == null)
   {
      var lockObj = new ReaderWriterLockSlim();
      var lockRef = new WeakReference<ReaderWriterLockSlim>(lockObj);
      _lockDic.AddOrUpdate(key, (k) => lockRef, (k, v) => 
      {
          var target = v.Target;
          if (target != null)
          {
             lockObj = target;
             return v;
          }
          return lockRef;
      });
   }
   return lockObj;
}



выглядит запутанно, тут я пытаюсь заткнуть любую щель
где неправильное значение проскочить
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359966
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

посчитал сколько занимает ReaderWriterSlimLock

https://dotnetfiddle.net/sltEUZ

95 байт

для 65536 блокировок это 6Мб, не считая расходы на словарь и хеши

выглядит достаточно эффективно, мне также интересно рассмотреть
идеальный случай, когда нужна одна блокировка на один ресурс

и как определить какое решение в итоге подойдёт лучше
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359986
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absinthe95 байт
у меня 68 получилось
Код: c#
1.
2.
3.
4.
5.
6.
			Int32 size = 65536;
			var used = GC.GetTotalMemory(false);
			var arr = new ReaderWriterLockSlim[size];
			Console.WriteLine((double)(GC.GetTotalMemory(false) - used) / size); // 4 массив указателей
			for(Int32 i = 0; i < size; i++) arr[i] = new ReaderWriterLockSlim();
			Console.WriteLine((double)(GC.GetTotalMemory(false) - used) / size); // 72 с массивом, без учета массива 68 (72-4)


При ограничении 65536 можно вместо ConcurrentDictionary использовать обычный массив. Займет 4,5 Мб (72*65536). Скорость выборки по индексу из массива намного быстрее.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359990
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheвыглядит запутанно, тут я пытаюсь заткнуть любую щель
где неправильное значение проскочить
Щели не заткнуты: первый поток прошел проверку и прервался вторым, второй прошел проверку и добавил, затем первый заменил то что добавил второй. В итоге два потока используют разные ReaderWriterLockSlim.
Надо добавление оборачивать в блокировку.
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
private ConcurrentDictionary<string, WeakReference<ReaderWriterLockSlim>> _lockDic;

public ReaderWriterLockSlim GetLock(string key)
{
   WeakReference<ReaderWriterLockSlim> lockRef;
   ReaderWriterLockSlim lockObj;
   if(!_lockDic.TryGet(key, out lockRef) || (lockObj = lockRef.Target) == null) // Проверяем без блокировки
   {
      lock(_lockDic) {
       if(!_lockDic.TryGet(key, out lockRef) || (lockObj = lockRef.Target) == null) // повторяем проверку
      {
        добавляем
...


Ну и при удалении аналогичная конструкция. Проверка, блокировка, еще раз проверка, удаление.

Мне кажется что будет тормозить при больших нагрузках из-за постоянных new ReaderWriterLockSlim() и удаления их сборщиком мусора, плюс эта блокировка.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359993
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima TНадо добавление оборачивать в блокировку.

зачем, если _lockDic.AddOrUpdate уже блокирует словарь
на изменение по ключу? пока происходит обновление по
ключу, никто не может по этому же ключу ничего сделать
даже TryGet в параллельном потоке будет ждать

два TryGet-а не нужно, потому что и первый нужен только
для скорости, чтобы не блокировало ресурс на чтение
из параллельных потоков, а второй точно не нужен,
потому что AddOrUpdate будет работать монопольно для ключа

почитал больше про WeakReference, пишут что ссылка может
моментально обнуляться, может надо устанавливать trackResurrection
в true, но тут я пока не понял, поможет ли это действительно
продлить жизнь ссылке
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359994
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В принципе можно без доп. блокировки если в твоем коде заменить _lockDic.AddOrUpdate() на _lockDict.GetOrAdd()

Но при этом усложняется удаление из _lockDic, т.к. пока ты удаляешь другой поток может успеть создать ReaderWriterLockSlim.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359995
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima TПри ограничении 65536 можно вместо ConcurrentDictionary использовать обычный массив. Займет 4,5 Мб (72*65536). Скорость выборки по индексу из массива намного быстрее.

если использовать обычный массив, придётся все операции
с ним обернуть в lock?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359996
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima TНо при этом усложняется удаление из _lockDic, т.к. пока ты удаляешь другой поток может успеть создать ReaderWriterLockSlim

AddOrUpdate не даст это сделать дважды, если кто-то успел добавить
элемент, то произойдёт Update, если нет то Add

выглядит вроде потокобезопасно, неужели я не прав?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359997
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

попробую прокомментировать что я пытаюсь сделать

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
private ConcurrentDictionary<string, WeakReference<ReaderWriterLockSlim>> _lockDic;

public ReaderWriterLockSlim GetLock(string key)
{
   WeakReference<ReaderWriterLockSlim> lockRef;
   ReaderWriterLockSlim lockObj;
   // если
   // в словаре нет ссылки
   // или ссылка есть, но она уже обнулилась
   // (при чём сразу получим строгую ссылку)
   if(!_lockDic.TryGet(key, out lockRef) || (lockObj = lockRef.Target) == null)
   {
      // создаём новый объект блокировки и новую слабую ссылку
      var lockObj = new ReaderWriterLockSlim();
      var lockRef = new WeakReference<ReaderWriterLockSlim>(lockObj);
      // добавим или обновим ссылку
      _lockDic.AddOrUpdate(key, 
          // в словаре ещё нет значения, значит добавим ссылку
          (k) => lockRef, 
          // в словарь уже кто-то успел добавить ссылку
          // тогда произведём обновление 
          (k, v) => 
         {
             // получим строгую ссылку из имеющейся слабой ссылки
             var target = v.Target;
             // если ссылка живая
             if (target != null)
             {
                // то присвоим её локальной переменной, чтобы вернуть
                lockObj = target;
                // оставляем слабую ссылку без изменений
                return v;
             }
             // если ссылка уже не живая, то обновим на новую ссылку
             return lockRef;
         });
   }
   // к этому моменту мы гарантировано имеем живую строгую ссылку
   // существующую в единственном экземпляре
   // гарантируется, что других строгих ссылок на объект блокировки
   // по ключу ни в одном потоке нет
   return lockObj;
}



что я делаю не так?
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39359998
absinthe
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dima T,

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

как закончу напишу результаты и код
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39360000
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheDima TНадо добавление оборачивать в блокировку.

зачем, если _lockDic.AddOrUpdate уже блокирует словарь
на изменение по ключу? пока происходит обновление по
ключу, никто не может по этому же ключу ничего сделать
даже TryGet в параллельном потоке будет ждать
Да, блокирует, но у тебя есть и другие строки в коде. То что ты в коде использовал атомарную операцию не делает весь код атомарным.
Пример
Поток 1Поток 2if(нет объекта в словаре)Поток 1 прерван потоком 2if(нет объекта в словаре)o = объект1AddOrUpdate(o)работаем с объект1o = объект2AddOrUpdate(o)работаем с объект2проболжаем работать с объект1
Понял зачем блокировка?

Остальное позже посмотрю, вечером.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39360002
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheчто я делаю не так?
Извиняюсь, не вник глубоко, сразу не понял что изнутри AddOrUpdate() ты перепроверяешь и меняешь lockObj. Возможно это атомарно отработает.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39360007
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Должно работать.

У тебя в коде косяк небольшой
Код: c#
1.
2.
3.
ReaderWriterLockSlim lockObj;
...
      var lockObj = new ReaderWriterLockSlim();


var лишний.

И не помешает вообще избавиться от
Код: c#
1.
2.
      var lockObj = new ReaderWriterLockSlim();
      var lockRef = new WeakReference<ReaderWriterLockSlim>(lockObj);


перенести их в добавление, а в обновлении оставлять старый WeakReference, а внутри него обновлять ссылку на новый ReaderWriterLockSlim.
примерно так
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
public ReaderWriterLockSlim GetLock(string key)
{
   WeakReference<ReaderWriterLockSlim> lockRef;
   ReaderWriterLockSlim lockObj;
   if(!_lockDic.TryGet(key, out lockRef) || (lockObj = lockRef.Target) == null)
   {
      _lockDic.AddOrUpdate(key, (k) => {
         lockObj = new ReaderWriterLockSlim();
         return new WeakReference<ReaderWriterLockSlim>(lockObj);
       }
       , (k, v) => 
      {
          lockObj = v.Target;
          if (lockObj == null)
          {
             lockObj = new ReaderWriterLockSlim();
             v.Target = lockObj;
          }
          return v;
      });
   }
   return lockObj;
}


absintheпочитал больше про WeakReference, пишут что ссылка может
моментально обнуляться, может надо устанавливать trackResurrection
в true, но тут я пока не понял, поможет ли это действительно
продлить жизнь ссылке
ИМХУ в твоем случае это не грозит, т.к. есть строгая ссылка в lockObj
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39360055
Var79
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TМне кажется что будет тормозить при больших нагрузках из-за постоянных new ReaderWriterLockSlim() и удаления их сборщиком мусора, плюс эта блокировка.
Кажется лучше тестировать чем гадать.
Вообще не понимаю зачем итернирование строк, ведь итерированные строки gc убирать не будет.
ТС делал какие то замеры сам, в release режиме?
gc-шного времени не занимает, а то что строки память жрут, кого вообще волнует? Память дешевая.
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39360098
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
absintheкак всетаки оценить какое решение лучше?
По ссылке из первого топика есть какие-то тесты . Их не пробовал запускать?
absinthe я начал писать проект
для тестирования, но возникли трудности создания условий
много потоков должны получать блокировку и нужно обеспечить,
чтобы где-то 10-20% потоков пытались заблокировать один ресурс
одновременно, не получается
Как вариант: сделай глобальную переменную и меняй ее из управляющего потока с какой-то частотой
Код: c#
1.
String main_key;


внутри тестового потока счетчик, каждый пятый/десятый проход работа с main_key
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
Int32 cnt = 0;
String key;
while(cnt < 100500) {
   cnt++;
   if(cnt % 5 == 0) {
     key = main_key;
   } else {
     key = "key " + cnt.ToString();
   }
   ... что-то делаем с key
}


тут тебе еще чтение/запись надо сэмулировать. Как вариант попытка записи у каждого четвертого, т.е. при cnt % 20 == 0
...
Рейтинг: 0 / 0
Блокировка по ключу
    #39360104
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Еще надо в "что-то делаем с key" добавить какой-нибудь расчет, который будет с разной скоростью идти. Например на вход каждому потоку давать какое-то число (всем потокам разные числа) и внутри "что-то делаем с key" посчитать сумму квадратов от 1 до этого числа.
...
Рейтинг: 0 / 0
39 сообщений из 39, показаны все 2 страниц
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Блокировка по ключу
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]