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[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[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]: Неблокирующая коллекция локов с функцией самоочистки
От: Sinix  
Дата: 12.08.16 12:09
Оценка: 3 (1) :)
Здравствуйте, mDmitriy, Вы писали:

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

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

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

По теме — вам надо comparer править в первую очередь, словарь и подпорки из Task — эт уже следствие.
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[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[6]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 12.08.16 14:24
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:

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

И какое отношение GC имеет к вызову Dispose?
Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: Sharov Россия  
Дата: 12.08.16 15:07
Оценка:
Здравствуйте, LWhisper, Вы писали:

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


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

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

Да, не прав. Мне казалось что финализатор таки дернет Dispose в случае IDisposable объекта, если мы это забыли сделать (про финализатор я в курсе). Shame on me.
Кодом людям нужно помогать!
Re[7]: Неблокирующая коллекция локов с функцией самоочистки
От: Lexey Россия  
Дата: 12.08.16 16:17
Оценка: +1 :)
Здравствуйте, LWhisper, Вы писали:

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


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

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


Так вызовите Dispose — в чем проблема?
Очевидно, что если GetOrAdd вернула не то, что было создано (или не создано) в переданной функции то, для предыдущего объекта надо вызвать Dispose()
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
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[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[4]: Неблокирующая коллекция локов с функцией самоочистки
От: LWhisper  
Дата: 15.08.16 17:26
Оценка:
Здравствуйте, TK, Вы писали:

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

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