Обработка параллельных заданий / потоков
Я пытаюсь провести рефакторинг своего проекта, и теперь я пытаюсь найти лучшие способы повышения производительности приложения.
Вопрос 1. SpinLock против блокировки
Для создания счетчика, который имеет лучшую производительность.
Interlocked.increament(ref counter)
Или же
SpinLock _spinlock = new SpinLock()
bool lockTaken = false;
try
{
_spinlock.Enter(ref lockTaken);
counter = counter + 1;
}
finally
{
if (lockTaken) _spinlock.Exit(false);
}
И если нам нужно увеличить другой счетчик, как counter2
Должны ли мы объявить другой SpinLock
объект? или достаточно использовать другой boolean
объект?
Вопрос 2. Обработка вложенных задач или лучшая замена
В этой текущей версии моего приложения я использовал задачи, добавляя каждую новую задачу в массив, а затем использовал Task.WaitAll()
После многих исследований я просто выдумал, что используя Parallel.ForEach
имеет лучшую производительность, но как я могу контролировать количество текущих потоков? Я знаю, что могу указать MaxDegreeOfParallelism
в ParallelOptions
параметр, но проблема здесь, каждый раз crawl(url)
метод работает, он просто создает другое ограниченное количество потоков, я имею в виду, если я установил MaxDegree
до 10, каждый раз crawl(url)
работает, еще один +10 будет создан, я прав? так как я могу предотвратить это? я должен использовать семафор и потоки вместо Paralel? Или есть лучший способ?
public void Start() {
Parallel.Invoke(() => { crawl(url) } );
}
crawl(string url) {
var response = getresponse(url);
Parallel.foreach(response.links, ParallelOption, link => {
crawl(link);
});
}
Вопрос 3. Уведомить, когда все задания (и вложенные задания) завершены.
И мой последний вопрос: как я могу понять, когда все мои работы закончились?
С уважением.
2 ответа
Здесь очень много заблуждений, и я хочу отметить лишь несколько.
Для создания счетчика, который имеет лучшую производительность.
Они оба делают, в зависимости от вашей конкретной ситуации
После многих исследований я просто предположил, что использование Parallel.ForEach имеет лучшую производительность
Это тоже очень подозрительно, и на самом деле просто неправильно. Еще раз это зависит от того, что вы хотите сделать.
Я знаю, что могу указать MaxDegreeOfParallelism в параметре ParallelOptions, но проблема здесь, при каждом запуске метода обхода (url), он просто создает другое ограниченное количество потоков
Еще раз, это неправильно, это ваша собственная деталь реализации и зависит от того, как вы это делаете. также TPL MaxDegreeOfParallelism
это всего лишь предложение, оно будет делать только то, что, по его мнению, эвристически лучше для вас.
я должен использовать семафор и потоки вместо Paralel? Или есть лучший способ?
Там ответ звучит оглушительно, да.
Хорошо, давайте посмотрим, что вы делаете. Вы говорите, что делаете гусеничный ход. Программа-обходчик обращается к Интернету каждый раз, когда вы получаете доступ к Интернету, сетевому ресурсу или файловой системе, которые (как говорится упрощенно) ожидают обратных вызовов порта завершения ввода-вывода. Это то, что известно как рабочая нагрузка ввода-вывода.
С задачами IO Bound мы не хотим связывать пул потоков с потоками, ожидающими портов завершения IO. Это неэффективно, вы используете ценные ресурсы, ожидая обратного вызова в потоках, которые фактически приостановлены.
Поэтому для работы, связанной с вводом-выводом, мы не хотим ускорять новые задачи и не хотим использовать Parallel ForEach для ожидания, используя потоки, ожидающие события. Наиболее подходящим современным шаблоном для задач ввода-вывода является async
а также await
шаблон.
Для работы с процессором (если вы хотите использовать как можно больше процессоров) разбейте пул потоков, используйте TPL Parallel или столько задач, сколько эффективно.
async
а также await
шаблон хорошо работает с портами завершения, потому что вместо ожидания ожидания обратного вызова он вернет потоки и позволит их использовать повторно. yehaa
...
Однако я предлагаю использовать другой подход, где вы можете воспользоваться async
а также await
а также управляет степенью параллелизма. Это позволяет вам хорошо относиться к пулу потоков, не тратя ресурсы на ожидание обратных вызовов и позволяя IO быть IO. Я даю тебе TPL DataFlow
ActionBlock
а также TransformManyBlocks
Эта тема немного выше простого рабочего примера, но я могу заверить вас, что это правильный путь для того, что вы делаете. Я предлагаю вам взглянуть на следующие ссылки.
- Стивен Клири Нет темы
- Стивен Клири Введение в поток данных
- MSDN Блоги Параллельное программирование с.NET
- Стивен Туб углубляется в жизнь Стивен Туб: Внутри потока данных TPL, в этом он даже рассказывает о примерах для сканеров.
- Несколько случайных блогов о потоке данных и сканерах Tpl Dataflow, пошаговое руководство. Часть 5
В заключение, есть много способов сделать то, что вы хотите сделать, и есть много технологий. Но главное, что у вас есть несколько очень сложных идей о параллельном программировании. Вам нужно поразить книги, поразить блоги и начать с нуля получать действительно твердые принципы проектирования, и перестать пытаться понять все это для себя, выбирая мелкие кусочки информации.
Удачи
Я бы посоветовал посмотреть на Microsoft Reactive Framework для этого. Вы можете написать свой Crawl
функционировать так:
public IObservable<Response> Crawl(string url)
{
return
from r in Observable.Start(() => GetResponse(url))
from l in r.Links.ToObservable()
from r2 in Crawl(l).StartWith(r)
select r2;
}
Затем, чтобы позвонить, попробуйте это:
IObservable<Response> crawls = Crawl("www.microsoft.com");
IDisposable subscription =
crawls
.Subscribe(
r => { /* process each response as it arrives */ },
() => { /* All crawls complete */ });
Готово. Он обрабатывает все потоки для вас. Просто NuGet "System.Reactive".