Обработка параллельных заданий / потоков

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

Вопрос 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 DataFlowActionBlock а также TransformManyBlocks


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

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

Удачи

Я бы посоветовал посмотреть на 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".

Другие вопросы по тегам