2013-04-20 2 views
3

Я думал о том, чтобы сделать мой веб-скребок многопоточным, а не как обычные потоки (egThread scrape = new Thread (Function);), но что-то вроде threadpool, где может быть очень большое количество потоков.Многопоточный веб-скребок?

Мой скребок работает с использованием петли for для очистки страниц.

for (int i = (int)pagesMin.Value; i <= (int)pagesMax.Value; i++) 

Так как же я мог бы использовать многопоточную функцию (содержащую цикл) с чем-то вроде threadpool? Раньше я никогда не использовал threadpool, и примеры, которые я видел, были довольно запутанными или неясными для меня.


Я изменил свой цикл в этом:

int min = (int)pagesMin.Value; 
int max = (int)pagesMax.Value; 
ParallelOptions pOptions = new ParallelOptions(); 
pOptions.MaxDegreeOfParallelism = Properties.Settings.Default.Threads; 
Parallel.For(min, max, pOptions, i =>{ 
    //Scraping 
}); 

бы, что работа или я что-то не так?

+1

Почему бы не использовать 'Parallel.For' или' TaskFactory.StartNew'? –

ответ

3

Лучше пойти с TPL, а именно Parallel.ForEach с использованием перегрузки с Partitioner. Он автоматически управляет рабочей нагрузкой.

FYI. Вы должны понимать, что больше потоков не означает быстрее. Я бы посоветовал вам провести некоторые тесты для сравнения unparametrized Parallel.ForEach и определения пользователя.

Update

public void ParallelScraper(int fromInclusive, int toExclusive, 
           Action<int> scrape, int desiredThreadsCount) 
    { 
     int chunkSize = (toExclusive - fromInclusive + 
      desiredThreadsCount - 1)/desiredThreadsCount; 
     ParallelOptions pOptions = new ParallelOptions 
     { 
      MaxDegreeOfParallelism = desiredThreadsCount 
     }; 

     Parallel.ForEach(Partitioner.Create(fromInclusive, toExclusive, chunkSize), 
      rng => 
      { 
       for (int i = rng.Item1; i < rng.Item2; i++) 
        scrape(i); 
      }); 
    } 

Примечание Вы могли бы быть лучше async в вашей ситуации.

+0

Я не совсем понимаю, почему я должен использовать 'Parallel.ForEach', а не' Parallel.For' – AlphaDelta

+0

'Parallel.ForEach' дает вам возможность автоматически выбирать размер раздела или настраивать его вручную. Добавляя параллельные опции к этому, вы можете принудительно установить желаемый уровень параллелизма (количество потоков), если это необходимо. Как правило, TPL эффективно управляет количеством потоков (масштабирование до количества ядер, если я не ошибаюсь), но для некоторых задач, связанных с IO, вы можете использовать больше потоков, чем ядра. –

2

Если вы считаете, что ваш веб-скребок, как использование для цикла, вы можете посмотреть на Parallel.ForEach(), который будет похож на цикл foreach; однако в этом он выполняет итерацию по перечисляемым данным. Параллельный.ForEach использовать несколько потоков для вызова тела цикла.

Для получения более подробной информации см Parallel loops

Update:

Parallel.For() очень похожи на Parallel.ForEach(), это зависит от контекста, как вы используете для или Еогеаспа петля.

5

Проблема с использованием потоков пулов заключается в том, что они проводят большую часть своего времени, ожидая ответа с веб-сайта. И проблема с использованием Parallel.ForEach заключается в том, что он ограничивает ваш параллелизм.

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

Основной поток создает Semaphore, например:

Semaphore _requestsSemaphore = new Semaphore(20, 20); 

20 была получена методом проб и ошибок. Оказывается, что ограничивающим фактором является разрешение DNS, и в среднем оно занимает около 50 мс.По крайней мере, это было в моей среде. 20 одновременных запросов были абсолютным максимумом. 15, вероятно, более разумно.

Основной поток, по существу петли, как это:

while (true) 
{ 
    _requestsSemaphore.WaitOne(); 
    string urlToCrawl = DequeueUrl(); // however you do that 
    var request = (HttpWebRequest)WebRequest.Create(urlToCrawl); 
    // set request properties as appropriate 
    // and then do an asynchronous request 
    request.BeginGetResponse(ResponseCallback, request); 
} 

Метод ResponseCallback, который будет называться на пул потоков, делает обработку, распоряжается ответ, а затем освобождает семафор так, что другой запрос можно сделать.

void ResponseCallback(IAsyncResult ir) 
{ 
    try 
    { 
     var request = (HttpWebRequest)ir.AsyncState; 
     // you'll want exception handling here 
     using (var response = (HttpWebResponse)request.EndGetResponse(ir)) 
     { 
      // process the response here. 
     } 
    } 
    finally 
    { 
     // release the semaphore so that another request can be made 
     _requestSemaphore.Release(); 
    } 
} 

Ограничивающий фактор, как я уже сказал, является разрешением DNS. Оказывается, разрешение DNS выполняется в вызывающем потоке (в этом случае основной поток). См. Is this really asynchronous? для получения дополнительной информации.

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

Возможно, вы, возможно, упростите это, используя Task и новый асинхронный материал в C# 5.0 (.NET 4.5). Я не достаточно знаком с тем, чтобы сказать, как это сделать.

0

Это идеальный сценарий для TLS Dataflow ActionBlock. Вы можете легко настроить его для ограничения параллелизма. Вот один из примеров из документации:

var downloader = new ActionBlock<string>(async url => 
{ 
    byte [] imageData = await DownloadAsync(url); 
    Process(imageData); 
}, new DataflowBlockOptions { MaxDegreeOfParallelism = 5 }); 

downloader.Post("http://msdn.com/concurrency "); 
downloader.Post("http://blogs.msdn.com/pfxteam"); 

Вы можете прочитать о ActionBlock (в том числе ссылочного примера), загрузив Introduction to TPL Dataflow.

+0

Я не думаю, что у меня есть функции Dataflow, я использую Visual Studio 2010, поэтому у меня только до .NET 4.0 нет 4.5 – AlphaDelta

+0

Я добавил тэг .NET 4.0 на ваш пост, поскольку это релевантная информация. См. Этот пост, если вы все еще заинтересованы в этом варианте: http://stackoverflow.com/questions/15338907/where-can-i-find-a-tpl-dataflow-version-for-4-0. –

0

Во время испытаний для нашего «Crawler-Lib Framework» я обнаружил, что параллельные попытки TPL или потоковой передачи не помогут вам получить пропускную способность, которую вы хотите иметь. Вы застряли на 300-500 запросов в секунду на локальной машине. Если вы хотите параллельно выполнять тысячи запросов, вы должны выполнить их асинхронный шаблон и обработать результаты параллельно. Наш движок Crawler-Lib (обработчик запросов с поддержкой рабочего процесса) делает это примерно с 10.000 - 20.000 запросов в секунду на локальном компьютере. Если вы хотите иметь быстрый скребок, не пытайтесь использовать TPL. Вместо этого используйте шаблон Async (Begin ... End ...) и запустите все ваши запросы в одном потоке.

Если многие из ваших запросов имеют тенденцию к тайм-ауту, скажем через 30 секунд, ситуация еще хуже. В этом случае решения на базе TPL получат уродливую плохую пропускную способность 5? 1? запросов в секунду. Асинхронный шаблон дает вам не менее 100-300 запросов в секунду. Механизм Crawler-Lib обрабатывает эту скважину и получает максимально возможные запросы. Допустим, ваш тэг TCP/IP настроен на 60000 исходящих подключений (максимальный максимум 65535, потому что для каждого подключения нужен исходящий порт), тогда вы получите пропускную способность 60000 соединений/30 секунд таймаут = 2000 запросов/секунду.

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