2013-04-20 33 views
3

我一直在考虑使我的网络刮板多线程,而不是像正常的线程(例如线程scrape =新线程(函数);)但像线程池那里可以有大量的线程。多线程web刮板?

我的刮刀通过使用for循环来刮擦页面。

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

那么我怎么能多线程函数(包含循环)像线程池?我以前从来没有使用过线程池,并且我看到的例子对我来说都很混乱或模糊。


我修改我循环到这一点:

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'? – 2013-04-20 00:42:43

回答

3

最好和TPL一起去,即Parallel.ForEach使用Partitioner超载。它自动管理工作负载。

仅供参考。你应该明白,更多的线程并不意味着更快。我建议你做一些测试来比较未参数化的Parallel.ForEach和用户定义的。

更新

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 2013-04-20 01:08:46

+0

'Parallel.ForEach'使您能够自动选择分区大小或手动设置分区大小。为此添加并行选项,如果需要,可以强制执行期望的并行性级别(线程数)。一般来说,TPL以有效的方式管理线程数量(如果我没有弄错,可扩展到核心数量),但对于某些IO界限任务,您可能希望使用比线程更多的线程。 – 2013-04-20 02:04:28

2

如果你认为你的web scraper喜欢使用for循环,你可以看看Parallel.ForEach()这将类似于foreach循环;然而,在这一点上,它遍历了一个可枚举的数据。 Parallel.ForEach使用多个线程来调用循环体。

有关详细信息,请参阅Parallel loops

更新:

的Parallel.For()非常相似Parallel.ForEach(),这取决于上下文就像您使用或的foreach循环。

5

使用线程池线程的问题是,他们花费大部分时间等待Web站点的响应。而使用Parallel.ForEach的问题是它限制了你的并行性。

我通过使用异步Web请求获得了最佳性能。我使用了Semaphore来限制并发请求的数量,并且回调函数进行了修改。

主线程创建Semaphore,就像这样:

Semaphore _requestsSemaphore = new Semaphore(20, 20); 

20是通过试验和错误的。事实证明,限制因素是DNS解析,平均而言,大约需要50 ms。至少,它在我的环境中。 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

这是TPL 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"); 

您可以通过下载Introduction to TPL Dataflow了解ActionBlock(包括引用的例子)。

+0

我不认为我有数据流功能,我使用的是Visual Studio 2010,因此我只能使用.NET 4.0而不是4.5 – AlphaDelta 2013-04-20 03:53:35

+0

我将.NET 4.0标记添加到了您的帖子中,因为这是相关信息。如果你仍然对这个选项感兴趣,请参阅这篇文章:http://stackoverflow.com/questions/15338907/where-can-i-find-a-tpl-dataflow-version-for-4-0。 – 2013-04-20 04:13:21

0

在对我们的“Crawler-Lib Framework”进行测试期间,我发现并行TPL或线程尝试不会获得您想要的吞吐量。您在本地机器上停留在每秒300-500个请求。如果要并行执行数千个请求,则必须执行它们的异步模式并并行处理结果。我们的Crawler-Lib Engine(一个支持工作流的请求处理器)可以在本地机器上以约10.000 - 20.000个请求/秒的速度执行此操作。如果你想有一个快速刮板,不要尝试使用TPL。而是使用异步模式(Begin ... End ...)并在一个线程中启动所有请求。

如果您的许多请求会超时,让我们在30秒后再说,情况会更糟。在这种情况下,基于TPL的解决方案会得到5的糟糕的不良吞吐量? 1?每秒请求数。异步模式每秒至少提供100-300个请求。 Crawler-Lib Engine可以很好地处理这个问题并获得最大可能的请求。假设你的TCP/IP粘性配置为有60000个出站连接(最大65535,因为每个连接都需要一个出站端口),那么你将获得60000个连接的吞吐量/30秒超时= 2000个请求/秒。