Подтвердить что ты не робот

Есть ли утечка памяти в реализации ConcurrentBag <T>?

Возможный дубликат:
Возможная память в ConcurrentBag?

Edit1:

Реальный вопрос. Можете ли вы подтвердить это или мой образец неправильно, и я не вижу ничего очевидного?

Я думал, что ConcurrentBag просто заменяет список unorderd. Но я был неправ. ConcurrentBag добавляет себя как ThreadLocal к создающему потоку, который в основном вызывает утечку памяти.

   class Program
    {
        static void Main(string[] args)
        {
            var start = GC.GetTotalMemory(true);
            new Program().Start(args);
            Console.WriteLine("Diff: {0:N0} bytes", GC.GetTotalMemory(true) - start);
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            Thread.Sleep(5000);
        }

        private void Start(string[] args)
        {
            for (int i = 0; i < 1000; i++)
            { 
                var bag = new ConcurrentBag<byte>();
                bag.Add(1);
                byte by;
                while (bag.TryTake(out by)) ;
            }
        }

Я могу сделать Diff 250 КБ или 100 ГБ в зависимости от того, сколько данных я добавляю в пакеты. Данные и мешки уходят.

Когда я врываюсь в это с помощью Windbg, и я делаю Параметр DumpHeap Concurrent

....

000007ff00046858        1           24 System.Threading.ThreadLocal`1+GenericHolder`3[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System],[System.Threading.ThreadLocal`1+C0[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]], mscorlib],[System.Threading.ThreadLocal`1+C0[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]], mscorlib],[System.Threading.ThreadLocal`1+C0[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]], mscorlib]]
000007feed812648        2           64 System.Collections.Concurrent.ConcurrentStack`1[[System.Int32, mscorlib]]
000007feece41528        1          112 System.Collections.Concurrent.CDSCollectionETWBCLProvider
000007ff000469e0     1000        32000 System.Threading.ThreadLocal`1+Boxed[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]]
000007feed815900     1000        32000 System.Collections.Concurrent.ConcurrentStack`1+Node[[System.Int32, mscorlib]]
000007ff00045530     1000        72000 System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]]

Когда я создаю пустой ConcurrentBag, чтобы некоторые рабочие потоки добавляли в него данные, ConcurrentBag и его данные будут там, пока создающий поток все еще жив.

Таким образом, я получил утечку памяти в несколько ГБ. Я "исправил" это, используя список и блокировки. ConcurrentBag может быть быстрым, но бесполезно, как простая замена списка с тем же самым сроком жизни объекта.

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

4b9b3361

Ответ 1

Вы правы, что ConcurrentBag создает копию ThreadLocal, на самом деле они оптимизированы для сценариев, где один и тот же поток читает и записывает данные в сумку: "... ConcurrentBag - это реалистичная реализация пакетов, оптимизированная для сценариев где тот же поток будет производить и потреблять данные, хранящиеся в сумке".

С другой стороны, я не вижу здесь странного поведения; нить живет, и совместная сумка живет. Когда поток заканчивается, GC выполнит эту работу.

Ответ 2

Из документации

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

и Когда использовать поточно-безопасную коллекцию

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

Я бы сказал, что ваши предположения о ConcurrentBag неверны. Во-первых, он не добавляет его в ThreadLocal, он использует локальное хранилище потоков для предоставления отдельных внутренних списков для каждого потока, который обращается к нему. Это больше, чем просто беспорядочный неупорядоченный список.

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

Сказав все это, я до сих пор не понял дополнительных функций ConcurrentBag, пока только.

Я нашел очень хорошее описание того, как ConcurrentBag использует отдельные списки и стоимость его методов в разных сценариях в Что такое ConcurrentBag ". Я хотел бы, чтобы это описание появилось в документации MSDN.

Лично я начну использовать ConcurrentBag намного больше, чтобы узнать о его особом поведении.

UPDATE:

Просто проверил этот пост Айенде, говоря, что "ThreadLocal, который использует ConcurrentBag, не ожидал, что будет много экземпляров. Это исправлено, и теперь может работать довольно быстро"

Ответ 3

Почему бы не переместить Console.WriteLine после второго GC.Collect()? В противном случае вы можете искать еще несколько объектов, чем вы планировали.

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

Ура!