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

Possible Duplicate:
Possible memoryleak in ConcurrentBag?

Edit1:

Актуальный вопрос Можете ли вы подтвердить это или мой образец неверен, и мне не хватает чего-то очевидного?

Я думал, что ConcurrentBag - просто замена неупорядоченного списка. Но я был неправ. 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 -type 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 может быть быстрым, но бесполезен в качестве простой замены для List с тем же временем жизни объекта.

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

 Trevor Pilley01 июн. 2012 г., 14:47
Это вопрос или утверждение?
 Greg B01 июн. 2012 г., 14:44
Что ты спрашиваешь?
 Alois Kraus01 июн. 2012 г., 14:47
Я спрашиваю, правда ли это или я что-то упустил очевидное. Я добавлю это.
 Julien Roncaglia01 июн. 2012 г., 14:58
Я запустил ваш код в последнем стабильном сборочном фреймворке из LINQPad с 100000 мешками по 1000 предметов, и моя разница отрицательная или нулевая ...
 Alois Kraus01 июн. 2012 г., 17:59
@VirtualBlackFox: Вы должны использовать дополнительный процесс и отладчик, чтобы быть уверенным, что происходит. Отрицательное значение указывает, что вы не должны использовать LINQPad.

Ответы на вопрос(3)

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

ConcurrentBag is a thread-safe bag implementation, optimized for scenarios where the same thread will be both producing and consuming data stored in the bag.

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

In mixed producer-consumer scenarios, ConcurrentBag is generally much faster and more scalable than any other concurrent collection type for both large and small workloads.

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

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

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

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

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

UPDATE:

Только что проверилэта почта Айенде говорит, что"ThreadLocal, which ConcurrentBag uses, didn’t expect to have a lot of instances. That has been fixed, and now can run fairly fast"

 01 июн. 2012 г., 15:16
Это не решает проблему: как освободить память без завершения всех связанных потоков (подумайте о потоках пула потоков)?
 01 июн. 2012 г., 15:30
Нет проблем. Так работает класс (и TLS). Кстати, точно такой же вопрос задавали и отвечали здесьstackoverflow.com/questions/5353164/…
 05 июн. 2012 г., 08:40
К сожалению, первая ссылка имеет одну полную остановку в конце. Исправлена. Что касается проблем, я думаю, что вы пытаетесь приспособить класс к сценарию, для которого он никогда не был создан. Класс, созданный для эффективной обработки публикацией / потребителем, никогда не будет столь же эффективным с точки зрения памяти, как простой массив с блокировкой. С другой стороны, контейнер без блокировки может масштабироваться до гораздо большего количества ядер, чем замок. Фактически, TPL в целом не был разработан для сверхэффективной массовой обработки - для этого нужны операции SSE2, а в .NET их нет. Моно делает, и я очень скучаю по ним.
 Alois Kraus01 июн. 2012 г., 18:09
Первая ссылка не работает. Вторая ссылка показывает довольно упрощенный вид этого. Это не касается ни одной из проблем, с которыми я столкнулся. Например. если вы создаете пакет целых чисел, вы используете не 4 байта на элемент, а 32 байта (экземпляр класса узла (20 байтов на x64), предыдущий указатель (8 байтов x64), следующий указатель (8 байтов x64), T item 4 байта) , Поэтому быстрый параллельный доступ к массовым данным в этом классе не обсуждается, поскольку накладные расходы довольно высоки.
Решение Вопроса

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

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

 01 июн. 2012 г., 15:12
Проблема в том, что память, кажется, остается в живых, пока поток не выйдет. Что если это длительный поток?
 Alois Kraus01 июн. 2012 г., 18:01
Я думал, что ConcurrentBag подойдет с TPL. Но если я не могу контролировать время жизни потока, я не могу быть уверен, когда память будет восстановлена. Это довольно ограничивающий фактор.
 Alois Kraus04 июн. 2012 г., 12:00
Я отмечу это как ответ, хотя я думаю, что локальная переменная ConcurrentBag никогда не должна выделять новый экземпляр TLS без способа его освобождения. Если вы создадите 1 миллион экземпляров ConcurrentBag в локальном методе, вы сохраните их, и у вас не будет возможности вызвать dispose на сумке, поскольку он не реализует IDisposable.
 12 нояб. 2012 г., 16:34
@Alois Kraus: ThreadLocal имеет финализатор, поэтому, если на ConcurrentBag нет ссылки, статическая нить будет очищена. Было бы предпочтительнее выставить Dispose на ConcurrentBag, но, по крайней мере, он не протекает.
 Alois Kraus12 нояб. 2012 г., 21:56
Вы проверили это на .NET 4.5. Для .NET 4.0 существует утечка, которая остается там до тех пор, пока поток, который ее удерживает, не завершится. Он стал лучше с .NET 4.5, но вы все еще не имеете явного контроля над своей памятью, что очень жаль. Структуры данных для элемента в сумке также потеряли большую часть своего подвига. Я все еще скучаю по неупорядоченной коллекции без блокировки в .NET, которая не имеет утечек или проблем с финализацией.

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

Вы можете также попытаться поместить все в своем главном в цикл, чтобы получить немного статистики. Даже если бы вы не переместили запись, вы, вероятно, позже увидели бы меньшие дельты.

Ура!

 Alois Kraus14 авг. 2012 г., 20:51
Список уже отфильтрован. Перечисленные распределения относятся только к распределению ConcurrentBag.

Ваш ответ на вопрос