1

Я пытаюсь проверить, как обрабатываются тяжелые объекты внутри .net BlockingCollection.Сборщик мусора не в состоянии восстановить ресурсы памяти

Я получаю тяжелые объекты, используя какой-либо API, и хочу обрабатывать его в нескольких потоках. Для этого у меня есть один поток, который читает тяжелые объекты и нажимает их на несколько потоков, где каждый поток имеет свою собственную блокирующую коллекцию, и каждый из этих потоков выводит объект из коллекции и процесса. Я ожидаю, что когда объект будет удален из всей коллекции, где бы он ни сидел, GC должен уметь его очищать. Эта очистка не происходит, и моя программа выходит из памяти.

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

Вопрос только в том, почему сборщик мусора не может высвободить ресурсы здесь, даже если объекты выходят за рамки.

public class DummyProcessor 
{ 
    List<BlockingCollection<object>> listOfBlockingCollection = null; 

    void ProcessCollection(object blockingCollection) 
    { 
     BlockingCollection<object> collection = (BlockingCollection<object>)blockingCollection; 

     while (collection.IsCompleted == false) 
     { 
      object heavyObject = collection.Take(); 

      CallExternalProcess(heavyObject); 
     } 
    } 

    private void CallExternalProcess(object heavyObject) 
    { 
     throw new NotImplementedException(); 
    } 

    public void Analyze(object heavyObject) 
    { 
     if (listOfBlockingCollection == null) 
     { 
      listOfBlockingCollection = new List<BlockingCollection<object>>(); 

      for (int i = 0; i < 25; i++) 
      { 
       BlockingCollection<object> coll = new BlockingCollection<object>(); 

       listOfBlockingCollection.Add(coll); 

       Thread pt = new Thread(new ParameterizedThreadStart(ProcessCollection)); 

       pt.Start(coll); 
      } 
     } 

     for (int i = 0; i < 25; i++) 
     { 
      listOfBlockingCollection[i].Add(heavyObject); 
     } 
    } 
} 
+0

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

+0

@ScottChamberlain Профайлер памяти Visual Studio работает очень медленно, возможно, мне нужно будет запустить его на ночь. Но я уверен, что эти объекты не выпущены даже после того, как они удалены из всей коллекции. Код, который я представил, - это обзор того, что происходит в исходном коде. Тот же объект добавляется к нескольким коллекциям, потому что каждая коллекция обрабатывается по-своему и записывается в другой хранилище данных в их собственном формате. – Bikswan

+0

Я был главным поклонником [dotMemory] (https://www.jetbrains.com/dotmemory/), он поставляется с Resharper, если он у вас есть, или у него также есть бесплатная 5-дневная пробная версия, если вы этого не сделаете. –

ответ

1

После запуска приложения через dotMemory я обнаружил, что тяжелые объекты производятся слишком быстро, что они накапливаются в различные BlockingCollection в большом количестве за пределами памяти компьютера.
Мой явный вызов GC.Collect работал не потому, что он выпускал использованный тяжелый объект, но добавлял паузу к созданию тяжелых объектов, давая больше времени потребительским потокам, чтобы очистить то, что уже есть в BlockingCollection.
Так что мне пришлось ввести ожидание между производителем тяжелых предметов и потребителем, чтобы я не злоупотреблял доступной памятью.Для этого я использую AutoResetEvent. Я звоню AutoResetEvent.WaitOne после того, как размер коллекции достигнет некоторого диапазона, и как только он окажется ниже диапазона, я звоню AutoResetEvent.Set, чтобы снова запустить продюсера.

Спасибо всем за ваш вклад в это.

2

Прямо сейчас я не вижу каких-либо угроз в этом коде, если Analyze вызывается в поточно-образом. Если есть некоторый анализ из dotMemory, добавьте его в вопрос.

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

В этом случае, вы создаете минимум 25 призрачных нитей, и, более важно, вы можете получить ситуацию, то ваш listOfBlockingCollection имеет более 25 пунктов: если какая-то нить уже из if пункта, а другой еще создает потоки и добавляет коллекции в список (например, вы можете проверить listOfBlockingCollection.Count). Один поток имеет по меньшей мере 2 МБ в памяти, и, поскольку объект IDisposable, он собирается не так быстро, как вам может захотеть.

Другая проблема с вашим кодом в том, что потоки 25 - это неэффективное количество потоков (только если у вас есть суперкомпьютер с 32 ядрами, это может быть опция) из-за контекстного переключения. Лучше использовать количество потоков, равное количеству ядер в вашей системе, или, что лучше, переключите свой код на Task -ориентированный (вы легко можете создать 25 задач для обработки и зациклиться внутри них).

Другой вариант, если вам нужен дополнительный рабочий процесс данных, вы можете использовать библиотеку TPL DataFlow с 25 ActionBlocks в своем приложении, начиная с 25 различных потоков. После окончания очереди тяжелых объектов вы можете легко send the Complete command по всем блокам и завершить свое исполнение.

+0

Спасибо. Да Мне нужно вызвать метод Analyze в потоковом режиме. Однако код, который я вставил здесь, является копией того, что я делаю в реальном проекте. Извини за это. – Bikswan

Смежные вопросы