2014-12-10 5 views
2

У меня есть C# консольное приложение работает на 64-битной Windows Server 2008 R2, который также размещен MSSQL Server 2005.Как правильно дросселировать многопоточное приложение?

Это приложение работает с помощью текстовых файлов, читает строку, разбивает строки значения в переменные, и вставляет данных в базу данных SQL, размещенную на локальном хосте.

Каждый текстовый файл представляет собой новый поток, каждая строка представляет собой новый поток, и каждый оператор вставки SQL выполняется под новым потоком.

Я подсчитываю количество каждого из этих типов потоков и уменьшаю их при завершении. Я задаюсь вопросом, что лучший способ заключается в «ПЭНД» будущие потоки от открытия ...

Например .. перед открыт новый SQL вставить нить я звоню ...

while(numberofcurrentthreads > specifiednumberofthreads) 
{ 
// wait 
} 
new.Thread(insertSQL); 

Если указано, что количество предметов было оценено до значения, которое не выбрасывает System.OutofMemoryExceptions. Большая часть угадывания занялась определением этого числа для каждого процесса.

Мои вопросы: есть ли более «эффективный» или правильный способ сделать это? Есть ли способ читать системную память, а не физическую память, и ждать, основываясь на заданном распределении ресурсов?

Чтобы проиллюстрировать эту идею ...

while(System.Memory < (System.Memory/2) || System.OutofMemory == true) 
{ 
// wait 
} 
new.Thread(insertSQL); 

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

В примере, если я говорю текстовые файлы процесса 2 одновременно, что отлично работает, когда оба текста файлы: < 300KB. Это не работает так хорошо, если один или два более 100 000 КБ.

Там также, кажется, «масло-зона», где все происходит наиболее эффективно. Где-то в среднем около 75% всех ресурсов ЦП. Выверните эти значения слишком высоко, и он будет работать на 100% CPU, но процесс медленнее, поскольку он не может идти в ногу.

+1

Существует буквально целая книга, написанная на эту тему, где сумма ответов «зависит от». –

+0

Похоже, что это лучше всего решить путем выбора размеров. Задайте максимальное количество потоков и узнайте, какая настройка работает лучше всего. – Thilo

+0

Создание и уничтожение потоков дорого, подумайте об использовании пула потоков вместо ... или даже встроенного пула потоков .net. Возможно, вы захотите взглянуть на ThreadPool.QueueUserWorkItem: http://msdn.microsoft.com /en-us/library/system.threading.threadpool.queueuserworkitem(v=vs.110).aspx – KristoferA

ответ

5

Сумасшествие - создать новый поток для каждого файла и для каждой строки и для каждой инструкции вставки SQL. Вероятно, вам будет гораздо лучше использовать три потока и цепочку производителей-потребителей, которые все свяжутся в потокобезопасных очередях. В C# это будет BlockingCollection.

Во-первых, вы создали две очереди, одна для линий, которые были считаны из текстового файла, и один для линий, которые были обработаны:

const int MaxQueueSize = 10000; 
BlockingCollection<string> _lines = new BlockingCollection<string>(MaxQueueSize); 
BlockingCollection<DataObject> _dataObjects = new BlockingCollection<DataObject>(MaxQueueSize); 

DataObject, кстати, является то, что я m вызывая объект, который вы будете вставлять в базу данных. Вы не говорите, что это такое. Это не имеет особого значения для целей этого обсуждения, но вы замените его любым типом, который вы используете для представления обработанной строки.

Теперь, вы создаете три темы:

  1. Потока, который читает текстовые файлы построчной строки и помещает строки в _lines очереди.
  2. Линейный процессор, который считывает строки один за другим из очереди _lines, обрабатывает его и создает DataObject, который затем помещает в очередь _dataObjects.
  3. Поток, который читает очередь _dataObjects и вставляет их в базу данных.

Помимо простоты (и это очень легко положить вместе), Есть много преимуществ для этой модели.

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

Во-вторых, ограничение размеров очередей предотвратит нехватку памяти. Когда поток чтения диска пытается вставить 10 001-й элемент в очередь, он будет ждать, пока поток обработки не удалит элемент. Это «блокирующая» часть BlockingCollection.

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

Это решение предотвращает проблему слишком большого количества потоков. У вас есть фиксированное количество потоков, все из которых работают как можно быстрее. И использование памяти ограничено, ограничивая количество вещей, которые могут находиться в очередях.

Решение также хорошо масштабируется. Если у вас есть файлы на нескольких дисках, вы можете добавить второй поток чтения файла, чтобы читать файлы с этого другого физического диска и помещает строки в одну очередь. BlockingCollection поддерживает несколько производителей и нескольких потребителей, поэтому добавление другого производителя - это совсем не проблема.

То же самое касается потребителей. Если вы обнаружите, что шаг обработки является узким местом, вы можете добавить еще один поток обработки. Он также будет считывать из очереди _lines и записывать в очередь dataObjects.

Однако, имея больше потоков, чем у вас, процессорные ядра, скорее всего, сделают вашу программу медленнее. Если у вас есть четырехъядерный процессор, создание 8 потоков обработки не принесет вам никакой пользы. Это замедлит работу, так как операционная система будет тратить много времени на коммутаторы контекста потока, а не на полезную работу.

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

Я создал простой пример этой модели, используя текстовые файлы для ввода и вывода. Это должно быть довольно легко расширить для вашей ситуации. См. Simple Multithreading и последующий, Part 2.

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

  • Нет связанных вопросов^_^