2011-09-27 29 views
7

.Net 4. ThreadLocal <> triển khai IDisposable. Nhưng có vẻ như việc gọi Dispose() không thực sự giải phóng các tham chiếu đến các đối tượng địa phương luồng đang được giữ.ThreadLocal <> và rò rỉ bộ nhớ

Mã này tái tạo các vấn đề:

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

Chủ đề lưu trữ dữ liệu địa phương một tham chiếu đến một đối tượng lớn. GC không thu thập các đối tượng lớn này nếu tham chiếu không bị vô hiệu theo cách thủ công. Tôi đã sử dụng Trình quản lý tác vụ để quan sát mức tiêu thụ bộ nhớ. Tôi cũng chạy bộ nhớ hồ sơ. Tôi đã chụp nhanh sau khi thu gom rác. Các hồ sơ cho thấy, đối tượng bị rò rỉ là bắt nguồn từ bởi GCHandle và được phân bổ với ở đây:

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 

Đó có vẻ là một lỗ hổng trong ThreadLocal <> thiết kế. Bí quyết với việc lưu trữ tất cả các đối tượng được phân bổ để làm sạch thêm là xấu xí. Bất kỳ ý tưởng về cách làm việc xung quanh đó?

+1

Bạn đang gỡ lỗi hoặc phát hành cho điều này? Ngoài ra, quản lý tác vụ không thực sự rất hữu ích cho những gì bạn đang đo –

+0

Bạn nên sử dụng 'GC.GetTotalMemory (true)' để đo bộ nhớ, nhưng điều đó cũng không đảm bảo rằng mọi thứ sẽ được thu thập. – Ray

+1

Đã in GC.GetTotalMemory(). Nó cho 335607644 khi tôi không số không ** dữ liệu ** lĩnh vực và 63268 khi tôi làm. – SergeyS

Trả lời

1

Bộ nhớ có thể đã được thu thập rác nhưng quá trình CLR vẫn chưa buông bỏ nó. Nó có xu hướng giữ bộ nhớ phân bổ cho một chút trong trường hợp nó cần nó sau này vì vậy nó không phải làm một phân bổ bộ nhớ đắt tiền.

+2

GC hoạt động khác nếu trường 'dữ liệu' được đánh số không. Plus Memory Profiler cho thấy đối tượng ThreadLocalData thực sự bắt nguồn từ đâu đó từ ThreadLocal <> internals. – SergeyS

1

Chạy trên .Net 4.5 DP, tôi không thấy bất kỳ sự khác biệt nào giữa cách nhấn R hoặc không có trong ứng dụng của bạn. Nếu thực sự có rò rỉ bộ nhớ trong 4.0, có vẻ như nó đã được sửa.

(4.5 là bản cập nhật tại chỗ, vì vậy tôi không thể kiểm tra 4.0 trên cùng một máy tính, xin lỗi.)