powered by simpleCommunicator - 2.0.53     © 2025 Programmizd 02
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Блокировка по ключу
25 сообщений из 39, страница 1 из 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
25 сообщений из 39, страница 1 из 2
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Блокировка по ключу
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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