|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
здравствуйте! помогите пожалуйста разобраться с блокировками по строковому ключу нашёл такую статью http://johnculviner.com/achieving-named-lock-locker-functionality-in-c-4-0/ там описывается несколько решений, самое быстрое это NamedReaderWriterLocker, но дело не только в скорости. меня смущает, что независимо от выбранного способа постоянно забивается память, либо интернированными строками, либо объектами блокировок. мне это совершенно не подходит. мне нравится использование ReaderWriterLockSlim, но не нравится, что он оседает в памяти, даже если больше не нужен и возможно даже не понадобится. можно как-то обеспечить существование блокировки в памяти, только пока есть хотя бы один читатель/писатель? нужны 100% гарантии, что писать может только один поток. интернирование не подходит, так как забивает память огромным количеством ключей. спасибо! ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 10:14 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
absintheможно как-то обеспечить существование блокировки в памяти, только пока есть хотя бы один читатель/писатель? нужны 100% гарантии, что писать может только один поток. Читать/писать куда? Может просто это "куда" блокировать? ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 11:37 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima T, читать значит не производить никаких действий, изменяющих данные писать значит добавлять, обновлять, удалять, и не только в БД ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 12:07 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima T, блокировка БД не подходит, так как изменяющее действие это в совокупности много разных действий не только в БД ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 12:09 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
absintheмне нравится использование ReaderWriterLockSlim, но не нравится, что он оседает в памяти, даже если больше не нужен и возможно даже не понадобится. Там есть RemoveLock(), вопрос только когда его вызывать? Можно например счетчик установленных блокировок отслеживать (CurrentReadCount, WaitingReadCount, WaitingUpgradeCount, WaitingWriteCount), когда в ноль уйдет, ставить блокировку на запись и удалять. Как вариант делать это периодически из другого потока, например раз в секунду прошел по всему _lockDict и удалил неиспользуемые. При удалении главная засада будет успеть удалить до того пока другой поток не захочет поработать с этой блокировкой. Т.е. проверил блокировки, их нет, а пока удалял кто-нибудь уже успел повиснуть в ожидании. ИМХУ сомневаюсь что есть готовые решения. Контроль ненужности сильно утяжеляет работу. ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 13:38 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima T, такое решение и напрашивается периодически очищать кеш блокировок, но оно мне не нравится, так как возникает угроза удаления в момент необходимости захвата блокировки, значит это тоже должно быть обёрнуто в какую-то блокировку со счётчиком блокировок ровно такая же проблема, но ближе к тому что нужно разве нет стандартного и универсального решения для блокировки по ключу? ConcurrentDictionary же работает без интернирования. может в MemoryCache объекты блокировки закидывать? не будет слишком уродливым решением? ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 14:11 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
absintheразве нет стандартного и универсального решения для блокировки по ключу? ConcurrentDictionary же работает без интернирования. ConcurrentDictionary просто подменяет хранилище интернов. Потому и работает. Но суть не меняется. Суть в том что надо где-то хранить соответствие строки с ключем и объекта блокировки. В примерах с интернированием сама строка выступает этим объектом, т.к. после интернирования одинаковые строки используют один и тот же объект String. ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 14:24 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
absintheможет в MemoryCache объекты блокировки закидывать? не будет слишком уродливым решением? Неуправляемое решение. Может лишнее долго лежать, а может наоборот удалить используемое если кэш маловат. Можно тут счетчик добавить и вытесняющий кэш реализовать Код: c# 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
На скорость работы эта добавка не должна сильно повлиять. При очистке просто удалять сколько-то с наименьшим last_access, получится тот же вытесняющий кэш. Сколько удалять можно например так вычислять: найти минимальный заблокированный и удалить половину меньше его. ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 15:44 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Такая еще мысль появилась про удаление. Код: c# 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.
Таким образом удаление будет гарантировать что одновременно несколько потоков не получат доступ к одному ключу. ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 16:15 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Еще мысль про кэш 19960299 . Удалять неиспользованные с момента прошлой очистки. И добавить минимально допустимый размер, ниже которого не опускаться. Код: c# 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.
... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 16:35 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima TConcurrentDictionary просто подменяет хранилище интернов. Потому и работает. Но суть не меняется. посмотрел исходники, они используют свою таблицу блокировок по хешу блокировкой является обычный new object() Dima TНеуправляемое решение. Может лишнее долго лежать, а может наоборот удалить используемое если кэш маловат. значит решение не просто уродливое, а неправильное ( Dima TМожно тут счетчик добавить и вытесняющий кэш реализовать а тут не нужно использовать volatile или Interlocked.Exchange чтобы не получить в разных потоках разные значения? Dima TТакая еще мысль появилась про удаление Dima TТаким образом удаление будет гарантировать что одновременно несколько потоков не получат доступ к одному ключу. я вижу тут проблему, что объекты в to_del будут дорабатывать когда появится новый клиент, а для него блокировки уже в _lockDict не найдётся получится что запись не будет полностью заблокирована идея мне нравится! вытесняющий кеш это то что нужно. только как обеспечить гарантии во время удаления, что выбранные блокировки для удаления никто не подхватит? interlocked exchange? ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 16:41 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
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 нужен именно для того чтобы таких подвисших вычислить и дать им корректно доработать. Смотри пример внимательно. ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 17:02 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima Tvolatile я добавил для счетчика, можно Interlocked.Exchange, но ИМХУ это лишнее извиняюсь, не заметил, действительно есть volatile Dima TМой пример внимательнее смотри, там уже решена эта проблема. Перед удалением из _lockDict объект блокируется на запись, поэтому если даже кто-то успеет его получить, то просто повиснет в ожидании пока эта блокировка не снимется прочитал внимательней, понял логику. ты перекидываешь блокировку с потоков, успевших схватить старую в момент её удаления с помощью освобождения старой блокировки внутри секции. тут точно не будет дедлока или косяка? этот ход конём смотрится подозрительно, но удивительно красиво ) ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 18:47 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima T, попробую реализовать логику по твоим примерам и погоняю на тестах спасибо за помощь! ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 18:48 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
absintheтут точно не будет дедлока или косяка? этот ход конём смотрится подозрительно, но удивительно красиво ) Дедлока не будет. В худшем случае попавшие на удаляемый ReaderWriterLockSlim подвиснут на ту самую паузу 50-100 мс, которая нужна для их ожидания, т.к. есть "зазор" между GetLock() и Enter***Lock() во время которого ссылка на объект уже получена, а блокировка еще не установлена. Вероятность маленькая что в зазор кто-то попадет, но есть, поэтому прежде чем это оптимизировать - добавь запись в лог о возникновении таких ситуаций. Будет много - есть куда оптимизировать. Решение не идеальное (как все решения с паузами), т.к. есть минимальная вероятность что при сильной перегрузке проца кто-то после паузы выйдет из зазора. Т.е. не успеет за время паузы это сделать. Как вариант в течении секунды каждые 50 мс проходить по to_del с проверкой. PS Если будешь писать по моим инструкциям - выкладывай сюда. Проверю и может еще кому пригодится. ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 19:42 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Еще после всех проверок в to_del на последок не помешает вызвать ReaderWriterLockSlim.Dispose() тогда даже если где-то кто-то, попав в зазор, вызовет Enter***Lock(), то на вызове получит исключение ObjectDisposedException (так MSDN обещает), это лучше чем испортить данные. ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 19:53 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
absinthe, Просто интересно, при решении какой задачи возникла данная проблема? Вдруг у меня похожее, а я ее не замечаю ... |
|||
:
Нравится:
Не нравится:
|
|||
02.12.2016, 22:17 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima T, возник ещё один вопрос можно использовать WeakReference? и периодически удалять ссылки с IsAlive == false? ... |
|||
:
Нравится:
Не нравится:
|
|||
03.12.2016, 17:23 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Cat2Просто интересно, при решении какой задачи возникла данная проблема? Вдруг у меня похожее, а я ее не замечаю есть ресурсы договоры и поставки операция с договором должна блокировать все операции со связанными поставками операция с поставкой должна блокировать все операции с договором связанных объектов на самом деле больше, поэтому блокировка общая и не связана с одной единственной записью БД, поэтому стратегии блокировки таблиц БД здесь не подойдут, ещё и другие есть операции не связанные с БД, загрузка и обработка файлов, отправка сообщений, сигналы ... |
|||
:
Нравится:
Не нравится:
|
|||
03.12.2016, 17:30 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Cat2, можно было бы обойтись блокировкой по интернированному ключу или с помощью словаря ключей, как в ссылке которую я скинул но так как ключей получается много, меня печалит что память со временем забивается этими ключами, их действительно много ... |
|||
:
Нравится:
Не нравится:
|
|||
03.12.2016, 17:32 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
absintheDima T, возник ещё один вопрос можно использовать WeakReference? и периодически удалять ссылки с IsAlive == false? Замена ReaderWriterLockSlim на WeakReference и проверка IsAlive == false перед удалением никак не решает проблем с многопоточностью. Проблемы все те же самые: первый поток проверил, второй создал объект, первый удалил. Вобщем без разницы что проверять ReaderWriterLockSlim не имеет блокировок или IsAlive == false. Этим только код усложнишь и лишнюю память займешь под объекты WeakReference. Основная память у тебя на строки (ключи) расходуется. На WeakReference ты их никак не заменишь. Как вариант: для экономии памяти можно в качестве ключа использовать не строку, а ее хэш. Это Int32 всего 4 байта. Код: c# 1. 2. 3. 4. 5.
В твоем случае никакой проблемы не будет если хэши двух разных ключей совпадут, в этом случае два разных ключа воспользуются одной общей блокировкой, т.е. в худшем случае вместо того чтобы обрабатываться параллельно они будут ожидать друг друга. Но это очень маловероятно, т.к. хэш функция подбирается так чтобы как можно реже давать одинаковый результат на разных строках. ... |
|||
:
Нравится:
Не нравится:
|
|||
03.12.2016, 19:46 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Еще мысль: если тебе пиковая производительность не критична, т.е. не смертельно что несколько разных несвязанных обработок будут синхронизироваться одним ReaderWriterLockSlim, то можно хэш уменьшить до нужного размера, что уменьшит максимальное количество элементов. Код: c# 1.
Тут ты гарантированно не будешь иметь _lockDict более 65536 элементов. Займет он максимум 1-2 Мб памяти. Можно вообще не заморачиваться на удаление. PS Тут я не уверен что ключ будет разнообразен, это надо потестить на твоих строках, возможно надо что-то другое, например Код: c# 1. 2.
... |
|||
:
Нравится:
Не нравится:
|
|||
03.12.2016, 20:03 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
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.
Dima TЕще мысль: если тебе пиковая производительность не критична, т.е. не смертельно что несколько разных несвязанных обработок будут синхронизироваться одним ReaderWriterLockSlim, то можно хэш уменьшить до нужного размера, что уменьшит максимальное количество элементов. я очень крепко думал на эту тему, когда смотрел на реализацию ConcurrentDictionary действительно, если разброс хешей по значениям будет равномерным, то вероятность одной блокировки по разным ключам мала зависит от количества хешей как всетаки оценить какое решение лучше? я начал писать проект для тестирования, но возникли трудности создания условий много потоков должны получать блокировку и нужно обеспечить, чтобы где-то 10-20% потоков пытались заблокировать один ресурс одновременно, не получается правильно ли я представил решение с WeakReference? правильно ли его использовать таким образом? ... |
|||
:
Нравится:
Не нравится:
|
|||
04.12.2016, 01:04 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
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.
выглядит запутанно, тут я пытаюсь заткнуть любую щель где неправильное значение проскочить ... |
|||
:
Нравится:
Не нравится:
|
|||
04.12.2016, 01:21 |
|
Блокировка по ключу
|
|||
---|---|---|---|
#18+
Dima T, посчитал сколько занимает ReaderWriterSlimLock https://dotnetfiddle.net/sltEUZ 95 байт для 65536 блокировок это 6Мб, не считая расходы на словарь и хеши выглядит достаточно эффективно, мне также интересно рассмотреть идеальный случай, когда нужна одна блокировка на один ресурс и как определить какое решение в итоге подойдёт лучше ... |
|||
:
Нравится:
Не нравится:
|
|||
04.12.2016, 01:36 |
|
|
start [/forum/topic.php?fid=20&msg=39359966&tid=1400172]: |
0ms |
get settings: |
8ms |
get forum list: |
11ms |
check forum access: |
3ms |
check topic access: |
3ms |
track hit: |
48ms |
get topic data: |
10ms |
get forum data: |
2ms |
get page messages: |
59ms |
get tp. blocked users: |
1ms |
others: | 16ms |
total: | 161ms |
0 / 0 |