C # Многопоточный файл IO (чтение) - PullRequest
3 голосов
/ 20 апреля 2010

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

Каждый предмет работы:
1. Откройте файл только для чтения
2. Обработка данных в файле
3. Записать обработанные данные в словарь

Мы хотели бы выполнить работу каждого файла в новом потоке? Возможно ли это и должно ли быть лучше, если мы будем использовать ThreadPool или создавать новые потоки, помня о том, что каждый элемент «работы» занимает всего 30 мс, однако возможно, что для обработки сотен файлов потребуется.

Любые идеи по повышению эффективности приветствуются.

РЕДАКТИРОВАТЬ: В настоящее время мы используем ThreadPool для обработки этого. Если у нас есть 500 файлов для обработки, мы циклически перебираем файлы и выделяем каждую «единицу обработки» в пул потоков, используя QueueUserWorkItem.

Подходит ли для этого пул потоков?

Ответы [ 8 ]

8 голосов
/ 16 сентября 2010

Я бы предложил вам использовать ThreadPool.QueueUserWorkItem(...), при этом потоки управляются системой и .net framework. Вероятность того, что вы попадаете в собственный пул потоков, намного выше. Поэтому я бы порекомендовал вам использовать Threadpool, предоставленный .net. Это очень удобно,

ThreadPool.QueueUserWorkItem(new WaitCallback(YourMethod), ParameterToBeUsedByMethod); 

YourMethod(object o){ Your Code here... }

Для получения дополнительной информации перейдите по ссылке http://msdn.microsoft.com/en-us/library/3dasc8as%28VS.80%29.aspx

Надеюсь, это поможет

2 голосов
/ 20 апреля 2010

Вместо того, чтобы иметь дело с потоками или напрямую управлять пулами потоков, я бы предложил использовать библиотеку более высокого уровня, такую ​​как Parallel Extensions (PEX):

var filesContent = from file in enumerableOfFilesToProcess
                   select new 
                   {
                       File=file, 
                       Content=File.ReadAllText(file)
                   };

var processedContent = from content in filesContent
                       select new 
                       {
                           content.File, 
                           ProcessedContent = ProcessContent(content.Content)
                       };

var dictionary = processedContent
           .AsParallel()
           .ToDictionary(c => c.File);

PEX будет обрабатывать управление потоками в соответствии с доступными ядрами и нагрузкой, в то время как вы сможете сосредоточиться на имеющейся бизнес-логике (вау, это звучит как реклама!)

PEX является частью .Net Framework 4.0, но в качестве части Reactive Framework .

также доступен резервный порт 3.5.
2 голосов
/ 20 апреля 2010

Я предлагаю вам иметь конечное количество потоков (скажем, 4), а затем иметь 4 пула работы. То есть Если у вас есть 400 файлов для обработки, то делите 100 файлов на потоки равномерно. Затем вы создаете потоки, переходите к каждой их работе и позволяете им работать, пока они не закончили свою конкретную работу.

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

1 голос
/ 21 апреля 2010

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

  1. Создайте рабочий класс, который выполняет обработку, и дайте ему процедуру обратного вызова для возврата результатов и статуса.
  2. Для каждого файла создайте рабочий экземпляр и поток для его запуска. Поместите нить в Queue.
  3. Очистите потоки от очереди до максимального значения, которое вы хотите запустить одновременно. Когда каждый поток завершается, идите, получите другой. Отрегулируйте максимум и измерьте пропускную способность. Я предпочитаю использовать Dictionary для хранения запущенных потоков с ключами ManagedThreadId.
  4. Чтобы рано остановиться, просто очистите очередь.
  5. Используйте блокировку вокруг ваших коллекций потоков, чтобы сохранить ваше здравомыслие.
1 голос
/ 20 апреля 2010

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

Вот некоторый пример кода с использованием CCR, Interleave будет хорошо работать здесь:

Arbiter.Activate(dispatcherQueue, Arbiter.Interleave(
    new TeardownReceiverGroup(Arbiter.Receive<bool>(
        false, mainPort, new Handler<bool>(Teardown))),
    new ExclusiveReceiverGroup(Arbiter.Receive<object>(
        true, mainPort, new Handler<object>(WriteData))),
    new ConcurrentReceiverGroup(Arbiter.Receive<string>(
        true, mainPort, new Handler<string>(ReadAndProcessData)))));

public void WriteData(object data)
{
    // write data to the dictionary
    // this code is never executed in parallel so no synchronization code needed
}

public void ReadAndProcessData(string s)
{
    // this code gets scheduled to be executed in parallel
    // CCR take care of the task scheduling for you
}

public void Teardown(bool b)
{
    // clean up when all tasks are done
}
0 голосов
/ 29 февраля 2012

Использование ThreadPool для каждой отдельной задачи - определенно плохая идея. Исходя из моего опыта, это больше влияет на производительность, чем помогает. Первая причина заключается в том, что для выделения потока для выполнения ThreadPool требуется значительное количество служебных данных. По умолчанию каждому приложению назначается собственный ThreadPool, который инициализируется с пропускной способностью ~ 100 потоков. Когда вы выполняете 400 операций параллельно, заполнение очереди запросами не занимает много времени, и теперь у вас есть ~ 100 потоков, все конкурирующих за циклы ЦП. Да. Платформа .NET отлично справляется с регулированием и приоритезацией очереди, однако я обнаружил, что лучше всего использовать ThreadPool для длительных операций, которые, вероятно, не будут выполняться очень часто (загрузка файла конфигурации или случайные веб-запросы). ). Использование ThreadPool для случайного запуска нескольких операций намного эффективнее, чем использование его для одновременного выполнения сотен запросов. Учитывая текущую информацию, лучший курс действий будет примерно таким:

  1. Создайте System.Threading.Thread (или используйте одну ветку ThreadPool) с очередью, в которую приложение может отправлять запросы на

  2. Используйте методы FileStream BeginRead и BeginWrite для выполнения операций ввода-вывода. Это приведет к тому, что .NET Framework будет использовать нативные API для потоков и выполнения IO (IOCP).

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

В настоящее время я пишу подобное приложение, и использование этого метода является одновременно эффективным и быстрым ... Без каких-либо потоков или удушения мое приложение использовало только 10-15% ЦП, что может быть приемлемо для некоторых операций в зависимости от обработки однако из-за этого мой компьютер работал медленнее, как если бы приложение использовало 80% ЦП. Это был доступ к файловой системе. Функции ThreadPool и IOCP не заботятся о том, что они перегружают компьютер, так что не запутайтесь, они оптимизированы для производительности, даже если эта производительность означает, что ваш жесткий диск сжимается, как свинья.

Единственная проблема, с которой я столкнулся, - это использование памяти, немного превышающее (50+ мегабайт) во время фазы тестирования с одновременным открытием примерно 35 потоков. В настоящее время я работаю над решением, аналогичным рекомендации MSDN для SocketAsyncEventArgs , использующим пул для одновременного выполнения x количества запросов, что в конечном итоге привело меня к этому сообщению на форуме.

Надеюсь, это поможет кому-нибудь принять решение в будущем:)

0 голосов
/ 20 апреля 2010

Общее правило использования ThreadPool: если вы не хотите беспокоиться о завершении потоков (или используете мьютексы для их отслеживания) или беспокоитесь об остановке потоков.

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

ThreadPool обычно более эффективен, если вы повторно используете потоки. Этот вопрос даст вам более подробное обсуждение.

Hth

0 голосов
/ 20 апреля 2010

Используйте ThreadPool.QueueUserWorkItem для выполнения каждой независимой задачи. Определенно не создавайте сотни потоков. Это может вызвать сильные головные боли.

...