ThreadLocal <> и утечка памяти - PullRequest
       13

ThreadLocal <> и утечка памяти

8 голосов
/ 27 сентября 2011

.Net 4. ThreadLocal <> реализует IDisposable. Но кажется, что вызов Dispose () на самом деле не освобождает ссылки на удерживаемые локальные объекты потока.

Этот код воспроизводит проблему:

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;

namespace ConsoleApplication2
{
    class Program
    {
        class ThreadLocalData
        {
            // Allocate object in LOH
            public int[] data = new int[10 * 1024 * 1024];
        };

        static void Main(string[] args)
        {
            // Stores references to all thread local object that have been created
            var threadLocalInstances = new List<ThreadLocalData>();
            ThreadLocal<ThreadLocalData> threadLocal = new ThreadLocal<ThreadLocalData>(() =>
            {
                var ret = new ThreadLocalData();
                lock (threadLocalInstances)
                    threadLocalInstances.Add(ret);
                return ret;
            });
            // Do some multithreaded stuff
            int sum = Enumerable.Range(0, 100).AsParallel().Select(
                i => threadLocal.Value.data.Sum() + i).Sum();
            Console.WriteLine("Sum: {0}", sum);
            Console.WriteLine("Thread local instances: {0}", threadLocalInstances.Count);

            // Do our best to release ThreadLocal<> object
            threadLocal.Dispose();
            threadLocal = null;

            Console.Write("Press R to release memory blocks manually or another key to proceed: ");
            if (char.ToUpper(Console.ReadKey().KeyChar) == 'R')
            {
                foreach (var i in threadLocalInstances)
                    i.data = null;
            }
            // Make sure we don't keep the references to LOH objects
            threadLocalInstances = null;
            Console.WriteLine();

            // Collect the garbage
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            Console.WriteLine("Garbage collected. Open Task Manager to see memory consumption.");
            Console.Write("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

Поток локальных данных хранит ссылку на большой объект. GC не собирает эти большие объекты, если ссылки не обнуляются вручную. Я использовал диспетчер задач для наблюдения за потреблением памяти. Я также запускаю профилировщик памяти. Я сделал снимок после того, как мусор был собран. Профилировщик показал, что пропущенный объект укоренен GCHandle и был выделен здесь:

mscorlib!System.Threading.ThreadLocal<T>.GenericHolder<U,V,W>.get_Boxed()
mscorlib!System.Threading.ThreadLocal<T>.get_Value()
ConsoleApplication2!ConsoleApplication2.Program.<>c__DisplayClass3.<Main>b__2( int ) Program.cs

Это кажется недостатком в дизайне ThreadLocal <>. Хитрость с хранением всех выделенных объектов для дальнейшей очистки ужасна. Любые идеи о том, как обойти это?

Ответы [ 2 ]

1 голос
/ 20 ноября 2011

Работая на .Net 4.5 DP, я не вижу разницы между нажатием R или нет в вашем приложении.Если в 4.0 действительно произошла утечка памяти, похоже, что она исправлена.

(4.5 - обновление на месте, поэтому я не могу протестировать 4.0 на том же компьютере, извините.)

1 голос
/ 27 сентября 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...