單執行緒爬蟲效能瓶頸在於:「分析」與「下載」無法並行、單一連線下載速度慢。多執行緒架構可解決:生產者-消費者模式,分析執行緒產出 URL,下載執行緒池並行消費。本文為系列第一部分,涵蓋架構分析、執行緒池啟動與數量控制。
+------------------+ URL Queue (Thread-Safe) +------------------+
| 分析執行緒 (1) | ─────────────────────────────────> | 下載執行緒池 (N) |
| (Producer) | Enqueue: 發現新 URL | (Consumers) |
+------------------+ Dequeue: 取得待下載 URL +------------------+
| |
| 發現新連結 | 下載網頁內容
v v
HTML Parser HTTP Client
ConcurrentQueue<string> 或 BlockingCollection<string>,執行緒安全HashSet<string> 記錄已處理 URL,避免重複下載using System.Threading;
// 執行緒啟動委派(無參數方法)
ThreadStart downloadWork = new ThreadStart(DownloadWorker);
// 執行緒陣列(類別層級欄位,便於後續管理)
Thread[] downloadThreads;
public void StartDownloadThreads(int threadCount)
{
downloadThreads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++)
{
downloadThreads[i] = new Thread(downloadWork);
downloadThreads[i].Name = $"Downloader-{i}";
downloadThreads[i].IsBackground = true; // 設為背景執行緒,主程式結束時自動結束
downloadThreads[i].Start();
}
}
ThreadStart 委派要求無參數、無回傳值方法。共享資料需透過類別層級欄位或閉包:
// 類別層級共享資料
private ConcurrentQueue _urlQueue = new ConcurrentQueue();
private HashSet _visitedUrls = new HashSet();
private readonly object _visitedLock = new object();
private void DownloadWorker()
{
while (true)
{
if (_urlQueue.TryDequeue(out string url))
{
// 下載邏輯
DownloadUrl(url);
}
else
{
// 隊列空,短暫休眠避免忙等待
Thread.Sleep(100);
}
}
}
| 策略 | 適用場景 | 實作方式 |
|---|---|---|
| 固定數量 | 簡單爬蟲、資源可控 | 建構函式傳入 int threadCount |
| 依 CPU 核心數 | CPU 密集型分析 | Environment.ProcessorCount * 2 |
| 依網路頻寬 | I/O 密集型下載 | 測試得出最佳值(通常 5-20) |
| 動態調整 | 長期運行服務 | 監控隊列長度、錯誤率、CPU 使用率動態增減 |
public class CrawlerConfig
{
public int MaxDownloadThreads { get; set; } = 10; // 最大下載執行緒
public int MaxConcurrentDomains { get; set; } = 5; // 同域名並發限制
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
public TimeSpan DelayBetweenRequests { get; set; } = TimeSpan.FromMilliseconds(500);
}
.NET 4.5+ 建議使用 Task 與 TPL Dataflow,優於手動 Thread 管理:
// 使用 BlockingCollection 作為緩衝區
var urlQueue = new BlockingCollection(boundedCapacity: 1000);
// 生產者
Task.Run(() =>
{
foreach (var url in DiscoverUrls())
urlQueue.Add(url);
urlQueue.CompleteAdding();
});
// 消費者:多個 Task 並行消費
var downloadTasks = Enumerable.Range(0, 10).Select(i => Task.Run(() =>
{
foreach (var url in urlQueue.GetConsumingEnumerable())
DownloadUrl(url);
})).ToArray();
Task.WaitAll(downloadTasks);
爬蟲多執行緒核心在於生產者-消費者解耦與執行緒安全隊列。手動 Thread 適合教學與簡單場景,生產環境強烈建議使用 Task、BlockingCollection、TPL Dataflow 或成熟框架。下一篇將深入 URL 去重、同步機制與執行緒結束判斷。