Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: TK Лес кывт.рф
Дата: 17.08.16 09:39
Оценка: +3 :))) :)
Здравствуйте, LWhisper, Вы писали:

LW>Как частное решение — годится. Как общее — нет.

LW>Не подходит для структур. Не подходит для ситуаций, когда вызов фабрики должен быть один или он достаточно требовательный ресурсам, чтобы дополнительные вызове были нежелательны.
LW>Но в целом — да, имеет право на жизнь.

Disposable структура? Да вы просто красавцы! Тут что либо ещё советовать — только портить
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re: Неблокирующая коллекция локов с функцией самоочистки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 11.08.16 16:42
Оценка: +4 :)
Здравствуйте, LWhisper, Вы писали:

LW>Класс предоставляет локи для синхронизации операций в других Concurrent-коллекциях. Например, в ConcurrentDictionary для методов с отложенной инициализации AddOrUpdate и GetOrAdd.

Ускользает мысль, зачем в ConcurrentDictionary синхронизировать операции, да еще и блокирующим образом? Он же Concurrent!
Re[3]: Неблокирующая коллекция локов с функцией самоочистки
От: Sinix  
Дата: 11.08.16 17:34
Оценка: 14 (3)
Здравствуйте, LWhisper, Вы писали:

LW>Такие вот они Concurrent.


It's by design. Копаем в сторону
http://sergeyteplyakov.blogspot.ru/2015/06/lazy-trick-with-concurrentdictionary.html
http://www.tomdupont.net/2013/12/concurrent-dictionary-getoradd-thread-safety.html

P.S. В CodeJam кстати этот момент тоже не поправлен.
Re[3]: Неблокирующая коллекция локов с функцией самоочистки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 11.08.16 18:38
Оценка: 14 (2) +1
Здравствуйте, LWhisper, Вы писали:

LW>Здравствуйте, samius, Вы писали:


S>>Ускользает мысль, зачем в ConcurrentDictionary синхронизировать операции, да еще и блокирующим образом? Он же Concurrent!

LW>В ConcurrentDictionary есть методы GetOrAdd и AddOrUpdate, которые принимают функции, возвращающие значения, которые необходимо добавить в словарь, если их там ещё нет.
LW>Проблема в том, что вызов этих функций не синхронизирован. Одновременный вызов из 10 потоков 10 раз вызовет переданный делегат и создаст 10 объектов, но лишь один из них окажется в словаре.
Т.е. проблема не в коллекции и синхронизировать надо не операции ConcurrentDictionary.
LW>Поэтому таким образом нельзя создавать IDisposable объекты, так как для них никогда не будет вызван Dispose, да и просто создавать объекты, которые как-либо влияют на внутреннюю экосистему продукта или внешние ресурсы. Такие вот они Concurrent.
Никогда? Прямо никогда нельзя и никогда не будет вызыван Dispose? Я предлагаю трезво оценить вероятность вызова из 10и потоков метода GetOrAdd с одним ключем. Сколько тысячных процента?

Кроме как через Lazy<T> вашу задачу можно решить гораздо проще, не создавая динамически syncRoot-ы, а используюя таблицу однажды созданных. Хэш от ключа делите на длину таблицы, что бы выбрать syncRoot. Длину таблицы выбирать от вероятности коллизии с разными ключами.
Re[5]: Неблокирующая коллекция локов с функцией самоочистки
От: Sinix  
Дата: 12.08.16 09:38
Оценка: 6 (1) +2
Здравствуйте, mDmitriy, Вы писали:

D>не факт, что с Lazy сработает всегда и объект будет создан только один...

Факт вообще-то, смотрим на LazyThreadSafetyMode.

D>мне вот пришлось городить что-то типа ConcurrentDictionary<TKey, Lazy<Task<TValue>>>, и внутри перед созданием еще проверять TryGetValue

Ну так вы неправильно ConcurrentDictionary используете, документацию надо читать Доппроверка из TryGetValue вам никак не поможет.

Вообще, ошибки при попытке смочь в многопоточные коллекции — регулярное дело. Вот тут товарищ доказывает, что ConcurrentDictionary поломан, попробуйте найти баг в доказательстве:
class Program
{
    static void DoTest()
    {
        var random = new Random();
        var start = new ManualResetEvent(false);
        var dict = new ConcurrentDictionary<string, int>();
        var results = new int[100];
        var threads = Enumerable.Range(0, 100).Select(i => new Thread(() =>
        {
            start.WaitOne(Timeout.Infinite, false);
            lock (results) results[i] = dict.GetOrAdd("key", x => i);
        }));
        threads.ToList().ForEach(x => x.Start());
        Thread.Sleep(100);
        start.Set();
        Thread.Sleep(100);
        var uniqueValues = results.Distinct().Select(x => x.ToString());
        Console.WriteLine("Unique values ({0}): {1}", uniqueValues.Count(), string.Join("/", uniqueValues));
    }

    static void Main(string[] args)
    {
        while (!Console.KeyAvailable)
        {
            DoTest();
        }

        Console.ReadKey();
    }
}

Sample output

Unique values (1): 96
Unique values (2): 0/95
Unique values (1): 95
Unique values (1): 96
Unique values (1): 97
Unique values (2): 0/97
Unique values (1): 96
Unique values (1): 96
Unique values (1): 97
Unique values (2): 0/98


Each time you see two values, it means that 2 threads received a different value for "key".



D>потому что просто Lazy пробивало на дубликаты при многопоточном обращении

Эва как! А можно пруф? Для текущего кода такое возможно или при косяке где-то в equality comparer, или при серьёзом баге в кишках ConcurrentDictionary. Такое однозначно надо воспроизводить и репортить.
Re: Неблокирующая коллекция локов с функцией самоочистки
От: vorona  
Дата: 11.08.16 13:52
Оценка: 7 (2)
Здравствуйте, LWhisper, Вы писали:

LW>Если больше никто эту блокировку не использует — убрать из коллекции. Идеально было бы вовсе отдать работу по подсчёту ссылок GC.


Используйте вместо Dictionary<TKey, Lock> System.Runtime.CompilerServices.ConditionalWeakTable<TKey, Lock>
Re[5]: Неблокирующая коллекция локов с функцией самоочистки
От: Lexey Россия  
Дата: 16.08.16 09:53
Оценка: 4 (2)
Здравствуйте, LWhisper, Вы писали:

TK>>Так вызовите Dispose — в чем проблема?

TK>>Очевидно, что если GetOrAdd вернула не то, что было создано (или не создано) в переданной функции то, для предыдущего объекта надо вызвать Dispose()
LW>Гениально! А как?

Как-то так, видимо:
IDisposable localValue = null;
var globalValue = dict.GetOrAdd(key, _ =>
{
    localValue = valueFactory(_);
    return localValue;
});
if (globalValue != localValue)
{
    localValue?.Dispose();
}
"Будь достоин победы" (c) 8th Wizard's rule.
Отредактировано 16.08.2016 9:56 Lexey . Предыдущая версия . Еще …
Отредактировано 16.08.2016 9:55 Lexey . Предыдущая версия .
Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: Sinix  
Дата: 12.08.16 12:09
Оценка: 3 (1) :)
Здравствуйте, mDmitriy, Вы писали:

D>компарер самописный был, да

D>и он точно глючил, потому что в результате мне пришлось везде прийти к простым строковым ключам
Бинго!

Я как-то напарывался на подобную ошибку, полдня ловил. Логика сравнения была кривой, что-то в духе a == b, b == c, a != c. Приятной отладки, ага
Но эт ещё не самое страшное. Самое — это когда ключик в словаре мутабельный и ключ изменяют после добавления в словарь. Вот такое может дооолго прятаться.

По теме — вам надо comparer править в первую очередь, словарь и подпорки из Task — эт уже следствие.
Re[4]: Неблокирующая коллекция локов с функцией самоочистки
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 11.08.16 20:07
Оценка: +2
Здравствуйте, Sinix, Вы писали:

S>P.S. В CodeJam кстати этот момент тоже не поправлен.


А это вот фик знает, что важнее — изредка, на коллизии словить повторный вызов конструктора значения, или на каждое создание генерить инстанс Lazy<T>. Можно, конечно, добавить в фабрику третью реализацию, но не замусорит ли это публичный API — вопрос.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK Blog
Re[6]: Неблокирующая коллекция локов с функцией самоочистки
От: Sharov Россия  
Дата: 12.08.16 11:33
Оценка: +2
Здравствуйте, Sinix, Вы писали:

S>Вообще, ошибки при попытке смочь в многопоточные коллекции — регулярное дело. Вот тут товарищ доказывает, что ConcurrentDictionary поломан, попробуйте найти баг в доказательстве:

S>
S>class Program
S>{
S>    static void DoTest()
S>    {
S>        var random = new Random();
S>        var start = new ManualResetEvent(false);
S>        var dict = new ConcurrentDictionary<string, int>();
S>        var results = new int[100];
S>        var threads = Enumerable.Range(0, 100).Select(i => new Thread(() =>
S>        {
S>            start.WaitOne(Timeout.Infinite, false);
S>            lock (results) results[i] = dict.GetOrAdd("key", x => i);
S>        }));
S>        threads.ToList().ForEach(x => x.Start());
S>        Thread.Sleep(100);
S>        start.Set();
S>        Thread.Sleep(100);
S>        var uniqueValues = results.Distinct().Select(x => x.ToString());
S>        Console.WriteLine("Unique values ({0}): {1}", uniqueValues.Count(), string.Join("/", uniqueValues));
S>    }

S>    static void Main(string[] args)
S>    {
S>        while (!Console.KeyAvailable)
S>        {
S>            DoTest();
S>        }

S>        Console.ReadKey();
S>    }
S>}
S>

S>[q]
S>Sample output

Хороший, точнее плохой, пример. Еще не разбираясь как работает и что делает код, поморщился вот от этого
lock (results) results[i] = dict.GetOrAdd("key", x => i);

Какой в этой строчке кода смысл?

А на SO все верно указано, надо дожидаться окончания всех потоков. Непонятно, что этот код тестирует или призван продемонстрировать.
Кодом людям нужно помогать!
Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: Lexey Россия  
Дата: 12.08.16 11:57
Оценка: +1 :)
Здравствуйте, Sharov, Вы писали:

S>Хороший, точнее плохой, пример. Еще не разбираясь как работает и что делает код, поморщился вот от этого

S>
S>lock (results) results[i] = dict.GetOrAdd("key", x => i);
S>

S>Какой в этой строчке кода смысл?

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

S>А на SO все верно указано, надо дожидаться окончания всех потоков. Непонятно, что этот код тестирует или призван продемонстрировать.


Призван продемонстрировать то, что GetOrAdd возвращает разные значения в разных потоках. Реально демонстрирует race при чтении/записи массива.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: Lexey Россия  
Дата: 12.08.16 16:17
Оценка: +1 :)
Здравствуйте, LWhisper, Вы писали:

LW>И какое отношение GC имеет к вызову Dispose?


Если класс реализует IDisposable по рекомендациям "лучших собаководов" из MS, то вполне прямое. Другое дело, что детерминированная финализация при этом не обеспечивается.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[3]: Неблокирующая коллекция локов с функцией самоочистки
От: TK Лес кывт.рф
Дата: 13.08.16 07:01
Оценка: +1 :)
Здравствуйте, LWhisper, Вы писали:

LW>Проблема в том, что вызов этих функций не синхронизирован. Одновременный вызов из 10 потоков 10 раз вызовет переданный делегат и создаст 10 объектов, но лишь один из них окажется в словаре. Поэтому таким образом нельзя создавать IDisposable объекты, так как для них никогда не будет вызван Dispose, да и просто создавать объекты, которые как-либо влияют на внутреннюю экосистему продукта или внешние ресурсы. Такие вот они Concurrent.


Так вызовите Dispose — в чем проблема?
Очевидно, что если GetOrAdd вернула не то, что было создано (или не создано) в переданной функции то, для предыдущего объекта надо вызвать Dispose()
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[9]: Неблокирующая коллекция локов с функцией самоочистки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 15.08.16 13:58
Оценка: +1 -1
Здравствуйте, LWhisper, Вы писали:

LW>Здравствуйте, samius, Вы писали:


S>>Вероятностью события A называют отношение числа m благоприятствующих этому событию исходов к общему числу n всех равновозможных несовместных элементарных исходов, образующих полную группу


LW>Не передёргивай. Проблема вполне очевидна. А когда речь идёт о комерческом ПО, выводы продиктованы законами Мёрфи, а не сухими определениями с институтской скамьи.


Нене, Мёрфи с его (Anything that can go wrong will go wrong) тут вообще не при чем, т.к. не подменял значение определения вероятности.
Re[6]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 12.08.16 14:24
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:

S>Причем здесь попадание в коллекцию. Этот объект рано или поздно (скорее рано) будет собран GC.

И какое отношение GC имеет к вызову Dispose?
Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 11.08.16 13:24
Оценка: :)
Всем привет.
Подскажите — как можно оптимизировать следующий код?
Класс предоставляет локи для синхронизации операций в других Concurrent-коллекциях. Например, в ConcurrentDictionary для методов с отложенной инициализации AddOrUpdate и GetOrAdd.
Задача — получить объект блокировки (который SyncRoot) для определенного ключа (чтобы не блокировать всю коллекцию). Если больше никто эту блокировку не использует — убрать из коллекции. Идеально было бы вовсе отдать работу по подсчёту ссылок GC.

    public sealed class ConcurrentLockProvider<TKey>
    {
        private readonly Dictionary<TKey, Lock> _locks;

        public ConcurrentLockProvider()
            : this(EqualityComparer<TKey>.Default)
        {
        }

        public ConcurrentLockProvider(IEqualityComparer<TKey> comparer)
        {
            _locks = new Dictionary<TKey, Lock>(comparer);
        }

        public IDisposable Acquire(TKey key)
        {
            lock(_locks)
            {
                Lock item;
                if(!_locks.TryGetValue(key, out item))
                {
                    item = new Lock(key, _locks);
                    _locks[key] = item;
                }
                Interlocked.Increment(ref item.Counter);
                return item;
            }
        }

        private sealed class Lock : IDisposable
        {
            private readonly TKey _key;
            private readonly Dictionary<TKey, Lock> _dic;
            
            public long Counter;

            public Lock(TKey key, Dictionary<TKey, Lock> locks)
            {
                _key = key;
                _dic = locks;
            }

            #region Implementation of IDisposable

            public void Dispose()
            {
                lock(_dic)
                {
                    if(Interlocked.Decrement(ref Counter) < 1)
                        _dic.Remove(_key);
                }
            }

            #endregion
        }
    }
gc lock collection c# .net threading concurrency
Re[6]: Неблокирующая коллекция локов с функцией самоочистки
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 11.08.16 20:43
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Ага. Но нам ничего не мешает вообще отдельную коллекцию сделать и в фабрику её не выставлять.


Это еще хуже, потому что неконсистентно. Лучше уж как в самом Lazy — три варианта. Можно даже штатный LazyThreadSafetyMode использовать вместо своего енума.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK Blog
Re[5]: Неблокирующая коллекция локов с функцией самоочистки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.08.16 03:00
Оценка: +1
Здравствуйте, AndrewVK, Вы писали:

S>>Я предлагаю трезво оценить вероятность вызова из 10и потоков метода GetOrAdd с одним ключем. Сколько тысячных процента?


AVK>Это зависит от количества коллизий, когда одновременно столкнутся два потока с запросом к одному ключу и цикл CompareExchange прокрутится больше одного раза. В общем случае такое оценить практически невозможно.


А в общем и не надо.
Re[3]: Неблокирующая коллекция локов с функцией самоочистки
От: Sinix  
Дата: 12.08.16 12:15
Оценка: +1
Здравствуйте, LWhisper, Вы писали:

S>>Начните с задачи, для которой вам потребовалась комбинация из ConcurrentDictionary и локов, дальше видно будет.

LW>Цепочка из нескольких кэшей, в процессе инициализации которых возможны задержки. Сейчас стреляет на общем локе на весь кэш. Думаю, спустя несколько версий, начнёт стрелять и на индивидуальных локах.

Ну да, ConcurrentDictionary + Lazy<T> решит проблему. Можно поискать что-то типа AsyncLazy(of T), если без блокировок надо.
Re[6]: Неблокирующая коллекция локов с функцией самоочистки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 12.08.16 12:35
Оценка: +1
Здравствуйте, Sinix, Вы писали:


S>
S>Unique values (1): 96
S>Unique values (2): 0/95
S>Unique values (1): 95
S>Unique values (1): 96
S>Unique values (1): 97
S>Unique values (2): 0/97
S>Unique values (1): 96
S>Unique values (1): 96
S>Unique values (1): 97
S>Unique values (2): 0/98
S>


Вероятно какая то задача не успела отработать по каким то причинам и соответственно поле с её индексом по умолчанию 0.
Для достоверности нужно было было бы

Task.WaitAll(threads.ToArray())

перед
var uniqueValues = results.Distinct().Select(x => x.ToString());
и солнце б утром не вставало, когда бы не было меня
Отредактировано 12.08.2016 13:35 Serginio1 . Предыдущая версия . Еще …
Отредактировано 12.08.2016 12:36 Serginio1 . Предыдущая версия .
Re[2]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 11.08.16 15:58
Оценка:
Здравствуйте, vorona, Вы писали:

V>Используйте вместо Dictionary<TKey, Lock> System.Runtime.CompilerServices.ConditionalWeakTable<TKey, Lock>

Замечательное решение, но увы — не подходит, так как сравнивает объекты по ссылкам, а в моём случае проверка должна выполняться средствами IEqualityComparer. :c
Re[2]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 11.08.16 17:25
Оценка:
Здравствуйте, samius, Вы писали:

S>Ускользает мысль, зачем в ConcurrentDictionary синхронизировать операции, да еще и блокирующим образом? Он же Concurrent!

В ConcurrentDictionary есть методы GetOrAdd и AddOrUpdate, которые принимают функции, возвращающие значения, которые необходимо добавить в словарь, если их там ещё нет.
Проблема в том, что вызов этих функций не синхронизирован. Одновременный вызов из 10 потоков 10 раз вызовет переданный делегат и создаст 10 объектов, но лишь один из них окажется в словаре. Поэтому таким образом нельзя создавать IDisposable объекты, так как для них никогда не будет вызван Dispose, да и просто создавать объекты, которые как-либо влияют на внутреннюю экосистему продукта или внешние ресурсы. Такие вот они Concurrent.
Re: Неблокирующая коллекция локов с функцией самоочистки
От: Sinix  
Дата: 11.08.16 17:25
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Задача — получить объект блокировки (который SyncRoot) для определенного ключа (чтобы не блокировать всю коллекцию). Если больше никто эту блокировку не использует — убрать из коллекции. Идеально было бы вовсе отдать работу по подсчёту ссылок GC.


Классическая проблема мангустов в почтовом ящике
Автор: Sinix
Дата: 01.06.14
. Симптомы: вы где-то сделали ошибку в цепочке рассуждений, в итоге получили проблему которую нельзя решить простыми средствами и ваша попытка решения по факту только создаёт новые. Ну и в итоге вместо того, чтобы лечить первопричину, вы пытаетесь перекинуть проблемы на рантайм (подсчёт ссылок в GC, угу).

Начните с задачи, для которой вам потребовалась комбинация из ConcurrentDictionary и локов, дальше видно будет.
Re[4]: Неблокирующая коллекция локов с функцией самоочистки
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 11.08.16 20:17
Оценка:
Здравствуйте, samius, Вы писали:

S>Я предлагаю трезво оценить вероятность вызова из 10и потоков метода GetOrAdd с одним ключем. Сколько тысячных процента?


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

S>Кроме как через Lazy<T> вашу задачу можно решить гораздо проще, не создавая динамически syncRoot-ы, а используюя таблицу однажды созданных.


+1
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK Blog
Re[5]: Неблокирующая коллекция локов с функцией самоочистки
От: Sinix  
Дата: 11.08.16 20:21
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>А это вот фик знает, что важнее — изредка, на коллизии словить повторный вызов конструктора значения, или на каждое создание генерить инстанс Lazy<T>.


Ага. Но нам ничего не мешает вообще отдельную коллекцию сделать и в фабрику её не выставлять. Тем более что основной сценарий очень специфический, словарь для кэша всяких вещей, которые гарантированно не устареют за время жизни процесса / долгоживущего сервиса.
Для всего остального смысл, имхо, теряется. Обычного ConcurrentDictionary хватит.
Re[4]: Неблокирующая коллекция локов с функцией самоочистки
От: mDmitriy Россия  
Дата: 12.08.16 09:07
Оценка:
Здравствуйте, Sinix, Вы писали:

S>It's by design. Копаем в сторону

S>http://sergeyteplyakov.blogspot.ru/2015/06/lazy-trick-with-concurrentdictionary.html
S>http://www.tomdupont.net/2013/12/concurrent-dictionary-getoradd-thread-safety.html
не факт, что с Lazy сработает всегда и объект будет создан только один...
мне вот пришлось городить что-то типа ConcurrentDictionary<TKey, Lazy<Task<TValue>>>, и внутри перед созданием еще проверять TryGetValue
потому что просто Lazy пробивало на дубликаты при многопоточном обращении
хотя уверенности, что это сработает всегда, нету
но пока такой вариант держит, не дает создавать лишнее
Re[6]: Неблокирующая коллекция локов с функцией самоочистки
От: mDmitriy Россия  
Дата: 12.08.16 10:43
Оценка:
Здравствуйте, Sinix, Вы писали:
D>>не факт, что с Lazy сработает всегда и объект будет создан только один...
S>Факт вообще-то, смотрим на LazyThreadSafetyMode.
используется, естественно

D>>мне вот пришлось городить что-то типа ConcurrentDictionary<TKey, Lazy<Task<TValue>>>, и внутри перед созданием еще проверять TryGetValue

S>Ну так вы неправильно ConcurrentDictionary используете, документацию надо читать Доппроверка из TryGetValue вам никак не поможет.
возможно, но на каком-то этапе разработки сработала, убирать не стал пока

S>Вообще, ошибки при попытке смочь в многопоточные коллекции — регулярное дело. Вот тут товарищ доказывает, что ConcurrentDictionary поломан, попробуйте найти баг в доказательстве:

чуть позже гляну

D>>потому что просто Lazy пробивало на дубликаты при многопоточном обращении

S>Эва как! А можно пруф? Для текущего кода такое возможно или при косяке где-то в equality comparer, или при серьёзом баге в кишках ConcurrentDictionary. Такое однозначно надо воспроизводить и репортить.
за текущий код не скажу, я в собственном наблюдал
компарер самописный был, да
и он точно глючил, потому что в результате мне пришлось везде прийти к простым строковым ключам
почему глючил — непонятно, там всего-то 2 метода реализовать
Re[3]: Неблокирующая коллекция локов с функцией самоочистки
От: Sharov Россия  
Дата: 12.08.16 10:49
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW> Поэтому таким образом нельзя создавать IDisposable объекты, так как для них никогда не будет вызван Dispose,


Это с чего вдруг?

LW>да и просто создавать объекты, которые как-либо влияют на внутреннюю экосистему продукта или внешние ресурсы. Такие вот они Concurrent.


Значит проектировать надо соотв. образом. Т.е. влиять на среду, если убедился, что сущ. в ед. экземпляре.
Кодом людям нужно помогать!
Re[4]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 12.08.16 10:50
Оценка:
Здравствуйте, Sinix, Вы писали:

S>http://sergeyteplyakov.blogspot.ru/2015/06/lazy-trick-with-concurrentdictionary.html

S>http://www.tomdupont.net/2013/12/concurrent-dictionary-getoradd-thread-safety.html

Это не ответ на вопрос, но применительно к ConcurrentDictionary решение интересное! Спасибо!
Re[4]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 12.08.16 10:53
Оценка:
Здравствуйте, samius, Вы писали:

S>Никогда? Прямо никогда нельзя и никогда не будет вызыван Dispose? Я предлагаю трезво оценить вероятность вызова из 10и потоков метода GetOrAdd с одним ключем. Сколько тысячных процента?

Да, никогда. В моём случае вероятность — 100%. В остальных случаях, при использовании по назначению (то есть в многопоточной среде) на длительном отрезке времени она стремится к 100%.

S>Кроме как через Lazy<T> вашу задачу можно решить гораздо проще, не создавая динамически syncRoot-ы, а используюя таблицу однажды созданных. Хэш от ключа делите на длину таблицы, что бы выбрать syncRoot. Длину таблицы выбирать от вероятности коллизии с разными ключами.

Интересное решение. Насколько я понимаю, нечто подобное как раз используется в ConcurrentDictionary.
Re[2]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 12.08.16 10:56
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Классическая проблема мангустов в почтовом ящике
Автор: Sinix
Дата: 01.06.14
. Симптомы: вы где-то сделали ошибку в цепочке рассуждений, в итоге получили проблему которую нельзя решить простыми средствами и ваша попытка решения по факту только создаёт новые. Ну и в итоге вместо того, чтобы лечить первопричину, вы пытаетесь перекинуть проблемы на рантайм (подсчёт ссылок в GC, угу).


S>Начните с задачи, для которой вам потребовалась комбинация из ConcurrentDictionary и локов, дальше видно будет.

Цепочка из нескольких кэшей, в процессе инициализации которых возможны задержки. Сейчас стреляет на общем локе на весь кэш. Думаю, спустя несколько версий, начнёт стрелять и на индивидуальных локах.
Re[5]: Неблокирующая коллекция локов с функцией самоочистки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.08.16 11:43
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Здравствуйте, samius, Вы писали:


S>>Никогда? Прямо никогда нельзя и никогда не будет вызыван Dispose? Я предлагаю трезво оценить вероятность вызова из 10и потоков метода GetOrAdd с одним ключем. Сколько тысячных процента?

LW>Да, никогда.
Я думаю что это слишком сильное утверждение.
LW>В моём случае вероятность — 100%. В остальных случаях, при использовании по назначению (то есть в многопоточной среде)
Т.е. вы используете не по назначению, т.е. в однопоточной среде? Нафига?
LW> на длительном отрезке времени она стремится к 100%.
У меня другие оценки. Допустим, есть 10 потоков, каждый в методе создания значения проводит не более 1/10 времени. Тогда 100% достижимо лишь при одном ключе. При увеличении кол-ва ключей вероятность ожидания устремится к нулю. Как в школе, для любого положительного эпсилон, существует такое кол-во ключей, при котором вероятность повтора будет меньше эпсилон. Как вы получили 100% — загадка.

S>>Кроме как через Lazy<T> вашу задачу можно решить гораздо проще, не создавая динамически syncRoot-ы, а используюя таблицу однажды созданных.

LW>Интересное решение. Насколько я понимаю, нечто подобное как раз используется в ConcurrentDictionary.
вероятно
Re[8]: Неблокирующая коллекция локов с функцией самоочистки
От: mDmitriy Россия  
Дата: 12.08.16 13:24
Оценка:
Здравствуйте, Sinix, Вы писали:
S>Но эт ещё не самое страшное. Самое — это когда ключик в словаре мутабельный и ключ изменяют после добавления в словарь. Вот такое может дооолго прятаться.
"Я знал, что когда-нибудь мы перейдем на эту дрянь"(с)

S>По теме — вам надо comparer править в первую очередь, словарь и подпорки из Task — эт уже следствие.

т.е., разобраться с компарером и оставить чистый Lazy?
спасибо
Re[6]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 12.08.16 13:59
Оценка:
S>У меня другие оценки. Допустим, есть 10 потоков, каждый в методе создания значения проводит не более 1/10 времени. Тогда 100% достижимо лишь при одном ключе. При увеличении кол-ва ключей вероятность ожидания устремится к нулю. Как в школе, для любого положительного эпсилон, существует такое кол-во ключей, при котором вероятность повтора будет меньше эпсилон. Как вы получили 100% — загадка.

Достаточно двух потоков, которые проводят в методе создания не более 1/10000 времени. Если время от времени они работают с одинаковыми ключами (напомню — речь о GetOrAdd и AddOrUpdate), то спустя неделю, месяц, год непрерывной работы коллизия будет.

В моём случае — это 10000 потоков и 180 ключей.

P.S. Естественно, ключи не только добавляются, но и удаляются. Возможно, твоё утверждение в обратном исходит из-за отсутствия этой детали?
Отредактировано 12.08.2016 14:23 LWhisper . Предыдущая версия .
Re[4]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 12.08.16 14:01
Оценка:
LW>> Поэтому таким образом нельзя создавать IDisposable объекты, так как для них никогда не будет вызван Dispose,
S>Это с чего вдруг?
Очевидно, с того, что они будут созданы, но в коллекцию не попадут.

S>Значит проектировать надо соотв. образом. Т.е. влиять на среду, если убедился, что сущ. в ед. экземпляре.

Мысль глубокая, но я её не понял.
Re[5]: Неблокирующая коллекция локов с функцией самоочистки
От: Sharov Россия  
Дата: 12.08.16 14:13
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>>> Поэтому таким образом нельзя создавать IDisposable объекты, так как для них никогда не будет вызван Dispose,

S>>Это с чего вдруг?
LW>Очевидно, с того, что они будут созданы, но в коллекцию не попадут.

Причем здесь попадание в коллекцию. Этот объект рано или поздно (скорее рано) будет собран GC.

S>>Значит проектировать надо соотв. образом. Т.е. влиять на среду, если убедился, что сущ. в ед. экземпляре.

LW>Мысль глубокая, но я её не понял.

Ну и ладно.
Кодом людям нужно помогать!
Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: Sharov Россия  
Дата: 12.08.16 15:07
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Здравствуйте, Sharov, Вы писали:


S>>Причем здесь попадание в коллекцию. Этот объект рано или поздно (скорее рано) будет собран GC.

LW>И какое отношение GC имеет к вызову Dispose?

Да, не прав. Мне казалось что финализатор таки дернет Dispose в случае IDisposable объекта, если мы это забыли сделать (про финализатор я в курсе). Shame on me.
Кодом людям нужно помогать!
Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.08.16 18:32
Оценка:
Здравствуйте, LWhisper, Вы писали:

S>>У меня другие оценки. Допустим, есть 10 потоков, каждый в методе создания значения проводит не более 1/10 времени. Тогда 100% достижимо лишь при одном ключе. При увеличении кол-ва ключей вероятность ожидания устремится к нулю. Как в школе, для любого положительного эпсилон, существует такое кол-во ключей, при котором вероятность повтора будет меньше эпсилон. Как вы получили 100% — загадка.


LW>Достаточно двух потоков, которые проводят в методе создания не более 1/10000 времени. Если время от времени они работают с одинаковыми ключами (напомню — речь о GetOrAdd и AddOrUpdate), то спустя неделю, месяц, год непрерывной работы коллизия будет.

Такой к оценке вероятности напомнил мне анекдот про блондинку и динозавра.

LW>В моём случае — это 10000 потоков и 180 ключей.

Даже если 10000 потоков 100% времени (т.е. 1/1) будут проводить в вычислении знаения, даже тогда при 180 ключах вероятность будет менее 1. Т.е. 100%.

LW>P.S. Естественно, ключи не только добавляются, но и удаляются. Возможно, твоё утверждение в обратном исходит из-за отсутствия этой детали?

Мое утверждение исходит из того, что где-то в курсе университета порядка 20 лет назад я прослушал курс лекций по теории вероятности. Прослушал — не значит что слушал. Это эквивалент оценки 2.5. Уже не неуд, но еще не уд.
А гугл подсказывает
Вероятностью события A называют отношение числа m благоприятствующих этому событию исходов к общему числу n всех равновозможных несовместных элементарных исходов, образующих полную группу
Re: Неблокирующая коллекция локов с функцией самоочистки
От: okon  
Дата: 14.08.16 10:44
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Всем привет.

LW>Подскажите — как можно оптимизировать следующий код?
LW>Класс предоставляет локи для синхронизации операций в других Concurrent-коллекциях. Например, в ConcurrentDictionary для методов с отложенной инициализации AddOrUpdate и GetOrAdd.
LW>Задача — получить объект блокировки (который SyncRoot) для определенного ключа (чтобы не блокировать всю коллекцию). Если больше никто эту блокировку не использует — убрать из коллекции. Идеально было бы вовсе отдать работу по подсчёту ссылок GC.

А для каких целей нужен "объект блокировки для определенного ключа" ?
У вас по ключу лежит некий объект , почему бы не дать данным объектам необходимую реализацию, например можно сделать как базовый класc, который будет хранить SyncRoot, или интерфейс в котором будет свойство дающее SyncRoot.

Например


public class Base
{

    object SyncRoot {get;}
 
    public Base()
    {  
        SyncRoot = new object();
    }
}


public class A : Base
{
   ....
   public A( .... )
        :base()
   {
   }

}


var concurentDict = new ConcurentDictionary();
concurentDict['A'] = new A(....);

lock(concurentDict['A'].SyncRoot)
{

}





public interface ISyncRootProvider
{

    object SyncRoot {get;}
}


public class A : ..., ISyncRootProvider
{

   public object SyncRoot {get;}
   ....
   public A( .... )
        
   {
     ...
     SyncRoot = new object();
   }

}


var concurentDict = new ConcurentDictionary();
concurentDict['A'] = new A(....);

lock(concurentDict['A'].SyncRoot)
{

}
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Re[8]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 15.08.16 11:28
Оценка:
Здравствуйте, samius, Вы писали:

S>Вероятностью события A называют отношение числа m благоприятствующих этому событию исходов к общему числу n всех равновозможных несовместных элементарных исходов, образующих полную группу


Не передёргивай. Проблема вполне очевидна. А когда речь идёт о комерческом ПО, выводы продиктованы законами Мёрфи, а не сухими определениями с институтской скамьи.
Re[8]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 15.08.16 11:36
Оценка:
Здравствуйте, Lexey, Вы писали:

L>Если класс реализует IDisposable по рекомендациям "лучших собаководов" из MS, то вполне прямое. Другое дело, что детерминированная финализация при этом не обеспечивается.

Вот ты сам себе и ответил.
А лучшие собаководы рекомендуют использовать финализаторы для освобождения неуправляемых ресурсов.
В среднестатистическом проекте в Dispose происходит всё что угодно, кроме этого.
И когда в потоке финализатора благодаря стараниям неопытного программиста кто-то начинает долбиться до мёртвого сервака — это не есть айс.
Re[4]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 15.08.16 17:26
Оценка:
Здравствуйте, TK, Вы писали:

TK>Так вызовите Dispose — в чем проблема?

TK>Очевидно, что если GetOrAdd вернула не то, что было создано (или не создано) в переданной функции то, для предыдущего объекта надо вызвать Dispose()
Гениально! А как?
Re[2]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 15.08.16 17:32
Оценка:
Здравствуйте, okon, Вы писали:

O>А для каких целей нужен "объект блокировки для определенного ключа" ?

O>У вас по ключу лежит некий объект , почему бы не дать данным объектам необходимую реализацию, например можно сделать как базовый класc, который будет хранить SyncRoot, или интерфейс в котором будет свойство дающее SyncRoot.

Это архитектурная ошибка особенность кода, обусловленная наличием зависимости одного кэша объектов от другого. Как следствие, выделение ресурсов происходит в GetOrAdd. Приходится захватывать блокировку до, а не после.
Re: Неблокирующая коллекция локов с функцией самоочистки
От: seregaa Ниоткуда http://blogtani.ru
Дата: 15.08.16 18:21
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Как следствие, выделение ресурсов происходит в GetOrAdd.


А если захват ресурсов выполнять уже после вызова GetOrAdd?

В функции-фабрике создавать легкие непроинициализированные объекты, а в переопределенной версии GetOrAdd запускать полную инициализацию перед тем, как вернуть объект наружу.

Lazy load в явном виде и с детерминированной инициализацией.
Мобильная версия сайта RSDN — http://rsdn.org/forum/rsdn/6938747
Автор: sergeya
Дата: 19.10.17
Re[6]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 17.08.16 08:47
Оценка:
Здравствуйте, Lexey, Вы писали:

L>Как-то так, видимо:

L>
L>IDisposable localValue = null;
L>var globalValue = dict.GetOrAdd(key, _ =>
L>{
L>    localValue = valueFactory(_);
L>    return localValue;
L>});
L>if (globalValue != localValue)
L>{
L>    localValue?.Dispose();
L>}
L>


Как частное решение — годится. Как общее — нет.
Не подходит для структур. Не подходит для ситуаций, когда вызов фабрики должен быть один или он достаточно требовательный ресурсам, чтобы дополнительные вызове были нежелательны.
Но в целом — да, имеет право на жизнь.
Re[8]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 17.08.16 22:00
Оценка:
Здравствуйте, TK, Вы писали:

TK>Здравствуйте, LWhisper, Вы писали:


LW>>Как частное решение — годится. Как общее — нет.

LW>>Не подходит для структур. Не подходит для ситуаций, когда вызов фабрики должен быть один или он достаточно требовательный ресурсам, чтобы дополнительные вызове были нежелательны.
LW>>Но в целом — да, имеет право на жизнь.

TK>Disposable структура? Да вы просто красавцы! Тут что либо ещё советовать — только портить

Какая часть "общего" нуждается в пояснении?
Нет, "мы" пока ещё не красавцы. Но есть к чему стремиться, а я верю в людей.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.