Использование памяти потоков С#

Я больше узнал о потоковой обработке, и я создал довольно простое приложение 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);
 }
}

Когда я запускаю этот код, процесс занимает приблизительно 560 МБ ОЗУ, пока все потоки работают/спадают.

По завершении процесса использование процесса сокращается до 125 МБ ОЗУ.

Мой вопрос в том, почему процесс использует 125 МБ ОЗУ в тот момент, когда само приложение (без примера потока) использует только 30 МБ ОЗУ?

Поддерживает ли он некоторые из потоков для возможного повторного использования или чего-то еще?

EDIT:

Из-за некоторых предложений по улучшению этого кода я хотел бы указать, что я не прошу о его улучшении, но чтобы определить причину такого поведения.

ИЗМЕНИТЬ 2:

Это не связанный с потоком, но я пробовал случай с большим string списком в памяти, и он не дал одинаковых результатов. Когда список был полностью загружен в память, потребовалось около 1,3 ГБ памяти, но после того, как список был установлен в NULL, и был вызван GC.Collect(), использование памяти упало до 30 МБ, как ожидалось.

код:

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

3 ответа

Часть памяти, используемая каждым потоком, освобождается, когда поток завершается. Поэтому потребление памяти падает с 560 МБ до 125 МБ, когда все фоновые потоки завершаются.

Остальная часть памяти выделяется на управляемой куче и будет освобождена сборщиком мусора.

Когда ваше приложение сидит без дела, новая память не выделяется, поэтому сборщик мусора не нужно запускать. Вы упомянули в комментарии, что принудительное использование GC с помощью GC.Collect() не помогло, но имейте в виду, что Thread имеет финализатор, поэтому он выживет в первой коллекции. Вы должны будете использовать

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

чтобы освободить память.

Это должно уменьшить использование памяти до уровня перед началом потоков.


Вы используете 64-битное приложение, которое просто создает много потоков. Во-первых, важно знать, что запуск сборки Debug приложения может дать разные результаты, чем запуск сборки Release, потому что переменные искусственно хранятся дольше, чем требуется в памяти. Таким образом, они не будут собраны как можно скорее.

Сборщик мусора сильно оптимизирован и будет срабатывать, когда:

  • Gen0 был полностью выделен (размер Gen0 основан на эвристике, такой как скорость, с которой ваше приложение выполняет выделение и т.д.);
  • Доступная память заканчивается;
  • Вызывается GC.Collect.

Причина, по которой GC не собирает память так быстро, как вы думали, потому что просто нет причины, чтобы GC собирал память. Если ваше приложение будет работать на 32-битной платформе, оно все равно будет иметь 1,5 ГБ памяти. В вашем случае вы используете 64-битное приложение: доступно много памяти. Однако, если вы используете "реальное" приложение с гораздо большим объемом выделения памяти, у вас будет другой результат и, вероятно, намного больше коллекций (и, следовательно, меньший рабочий набор).

Наконец, вызов GC.Collect часто не нужен и может испортить эвристику GC, которая может сильно повлиять на производительность вашего приложения, поскольку GC.Collect инициирует сбор всех поколений.

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


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

Что-то вроде этого (за вычетом отладочной информации) будет очень экономно использовать ваше потребление памяти. И ваш процессор должен быть "loafing" во время операции (статическое ключевое слово из моего тестирования в консольном приложении)..Net framework 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");
}
</long></long>

licensed under cc by-sa 3.0 with attribution.