ThreadLocal<>和内存泄漏

发布于 2024-12-06 10:54:13 字数 2675 浏览 0 评论 0 原文

.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<> 中的一个缺陷。设计。存储所有分配的对象以供进一步清理的技巧是丑陋的。关于如何解决这个问题有什么想法吗?

.Net 4. ThreadLocal<> implements IDisposable. But it seems that calling Dispose() doesn't actually release references to thread local objects being held.

This code reproduces the problem:

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();
        }
    }
}

Thread local data stores a reference to a large object. GC doesn't collect these large objects if references are not nulled manually. I used Task Manager to observe memory consumption. I also run memory profiler. I made a snapshot after garbage was collected. The profiler showed that leaked object is rooted by GCHandle and was allocated with here:

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

That seems to be a flaw in ThreadLocal<> design. The trick with storing all allocated objects for further cleanup is ugly. Any ideas on how to work around that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

秋千易 2024-12-13 10:54:13

内存可能已被垃圾回收,但 CLR 进程尚未释放它。它倾向于保留分配的内存一段时间,以备以后需要时使用,因此不必进行昂贵的内存分配。

The memory has probably been garbage collected but the CLR process hasn't let go of it yet. It tends to hold onto allocated memory for a bit in case it needs it later so it doesn't have to do an expensive memory allocation.

高跟鞋的旋律 2024-12-13 10:54:13

在 .Net 4.5 DP 上运行,我认为在应用程序中按 R 与不按 R 之间没有任何区别。如果 4.0 中确实存在内存泄漏,那么它似乎已得到修复。

(4.5是就地更新,所以我无法在同一台计算机上测试4.0,抱歉。)

Running on .Net 4.5 DP, I don't see any difference between pressing R or not in your application. If there actually was a memory leak in 4.0, it seems it was fixed.

(4.5 is a in-place update, so I can't test 4.0 on the same computer, sorry.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文