【问题标题】:c# thread memory usagec#线程内存使用
【发布时间】:2016-02-24 03:08:02
【问题描述】:

我正在学习更多关于线程的知识,并使用以下代码创建了一个相当简单的 WPF 应用程序(x64 平台构建)

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        for (var i = 0; i <= 20000; i++)
        {
            Thread thread = new Thread(Test);
            thread.IsBackground = true;

            thread.Start();
        }
    }

    public void Test()
    {
        Thread.Sleep(20000);
    }
}

当我运行此代码时,进程在所有线程都在运行/休眠时占用大约 560MB 的 RAM。

完成后,进程使用率降至大约 125 MB 的 RAM。

我的问题是,当应用程序本身(没有线程示例)仅使用 30 MB 的 RAM 时,为什么此时进程使用 125 MB 的 RAM?

它是否使某些线程保持活动状态以供可能的重用/使用或发生其他事情?

编辑:

由于对如何改进此代码的一些建议,我想指出,我不是在寻求改进它的方法,而是要查明这种行为的原因。

编辑 2:

这与线程无关,但我尝试了一个在内存中有一个大的string 列表的案例,但它没有产生相同的结果。当 list 完全加载到内存中时,大约需要 1.3 GB 的内存,但在 list 设置为 NULL 并调用 GC.Collect() 后,内存使用率按预期下降到 30 MB。

代码:

public partial class MainWindow : Window
{
    List<string> stringArray = new List<string>();

    public MainWindow()
    {
        InitializeComponent();


        for (var i = 0; i <= 100000000; i++)
        {
            //Thread thread = new Thread(Test);
            //thread.IsBackground = false;

            //thread.Start();

            stringArray.Add("Some very long string to pump up the memory volume 2 reloaded");
        }

        stringArray = null;
        GC.Collect();
    }



}

【问题讨论】:

  • 当线程终止时,线程对象在未来某个不确定的时间点有资格进行垃圾回收。
  • 对于初学者来说,每个线程都分配了一个默认的 1MB 堆栈,因此您在那里添加了大约 200MB。
  • @Aron 如果每个线程都被外部的东西阻塞,那么有很多线程是有原因的......
  • 还有一种可能性。当 .net 分配内存(这很昂贵)时,即使在它被释放后,.net 也可能不会释放它,因此您可能会看到 125 mb。正如我所说,请转储,并使用 windbg 进行分析。该检查还将揭示我认为接近 95 MB 的空闲内存量(可供托管堆和堆栈使用)
  • 20k 个对象不算什么,即使涉及最终确定,这也将占用接近零的内存。当线程完成时,线程堆栈被释放。线程退出时堆栈不会保持活动状态。为什么会呢?这个问题是有效的。到目前为止,有很多评论和支持的 cmets,但没有人在寻找答案方面取得任何进展。

标签: c# multithreading


【解决方案1】:

每个线程使用的部分内存在线程完成时被释放。这就是当所有后台线程完成时内存消耗从 560 MB 下降到 125 MB 的原因。

剩余的内存分配在托管堆上,将被垃圾收集器释放。

当您的应用程序处于空闲状态时,不会分配新内存,因此不需要运行垃圾收集器。您在评论中提到使用GC.Collect() 强制GC 没有帮助,但请记住Thread 已实现终结器,因此它将在第一个集合中存活。您将不得不使用

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

确保内存被释放。

这应该将内存使用量减少到启动线程之前的水平。

【讨论】:

    【解决方案2】:

    您正在运行一个 64 位应用程序,它只创建了很多线程。首先,重要的是要知道运行应用程序的 Debug 构建可能会产生与运行 Release 构建不同的结果,因为变量被人为地保存在内存中的时间超过了所需的时间。因此,它们不会被尽快收集。

    垃圾收集器经过高度优化,将在以下情况下触发:

    • Gen0 已完全分配(Gen0 的大小基于启发式方法,例如您的应用程序执行分配的速率等);
    • 可用内存即将耗尽;
    • GC.Collect 被调用。

    GC 没有像您想象的那样快速收集内存的原因是因为 GC 根本没有理由收集内存。如果您的应用程序将在 32 位平台上运行,它仍然有 1.5GB 的可用内存。在您的情况下,您正在运行一个 64 位应用程序:有足够的可用内存。但是,如果您正在运行具有更多内存分配的“真实”应用程序,您将得到不同的结果,并且可能会有更多的集合(因此工作集更小)。

    最后,调用 GC.Collect 通常是不必要的,并且会扰乱 GC 的启发式算法,从而严重影响应用程序的性能,因为 GC.Collect 会触发所有代的收集。

    https://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx

    【讨论】:

      【解决方案3】:

      您的线程机制是问题的一部分。除了在执行过程中占用大量 CPU 时间(减少垃圾收集的机会)之外,它还会影响您的内存使用。

      这样的事情(减去调试信息)将使您的内存消耗相当均匀。并且您的 CPU 在操作期间应该“闲逛”。(静态关键字来自我在控制台应用程序中的测试)。需要 .Net 框架 4.0 或更高版本。如果您受限于以前的 .net 版本,那么我建议您在开始新任务之前稍作停顿,以让垃圾收集发挥作用。

      private static void ThreadStuff()
      {
          long startSet = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64;
          List<long> endSet = new List<long>();
          for (var i = 0; i <= 20000; i++)
          {
              Action Act = new Action(() => Test(i));
      
              Task Tsk = new Task(Act);
              Tsk.Start();
              endSet.Add(System.Diagnostics.Process.GetCurrentProcess().WorkingSet64);
              int worker;
              int ioCompletion;
              ThreadPool.GetMaxThreads(out worker, out ioCompletion);
              Console.WriteLine(worker);
              Console.WriteLine(ioCompletion);
          }
          Console.WriteLine(startSet.ToString("###,###,###,###,###,###.####"));
          Console.WriteLine(endSet.Average().ToString("###,###,###,###,###,###.####"));
      }
      public static void Test(int Index)
      {
          Thread.Sleep(2000);
          Console.WriteLine(Index.ToString() + " Done");
      }
      

      【讨论】:

      • 我看到您正在使用任务,但是如果我将代码更改为前台线程怎么办?除此之外,我并不是在寻找优化代码的方法,而是寻找内存使用量从未低于 100 MB 的原因,即使所有线程都在几分钟前完成。
      • 这是一个他不想解决的问题的解决方案。他只是想知道为什么会发生这种情况。
      • @usr: '为什么'会发生是因为他调用线程的机制使用不同的机制可以解决问题。
      • stackoverflow.com/questions/6331389/… 的一些解释比我能提供的更好。
      • @paul 谢谢你的链接。我试图通过添加一个List&lt;string&gt; 来测试它,内存中大约有 1 亿条记录,然后清除它。当列表已满时,内存使用量约为 1.3 GB。将列表设置回 null 后,GC 成功了,我的内存使用量降至 30 MB
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-08
      • 1970-01-01
      • 1970-01-01
      • 2011-09-13
      • 1970-01-01
      • 2011-09-16
      相关资源
      最近更新 更多