设计思路
- 任务队列:使用通道来创建一个任务队列,用于存放需要爬取的网页URL。
- 工作池:限制同时运行的Goroutine数量为10,形成一个工作池。每个Goroutine从任务队列中获取URL,爬取网页并将结果通过另一个通道返回。
- 结果收集:使用一个结果通道来收集各个Goroutine爬取的结果。
- 资源管理:确保在所有任务完成后,能够正确关闭所有通道,避免死锁和资源泄漏。
关键代码实现
package main
import (
"fmt"
"sync"
)
// 模拟爬取网页的函数
func crawl(url string) string {
// 实际爬取逻辑,这里简单返回URL
return "Crawled: " + url
}
func worker(taskChan <-chan string, resultChan chan<- string, wg *sync.WaitGroup) {
defer wg.Done()
for url := range taskChan {
result := crawl(url)
resultChan <- result
}
}
func main() {
urls := []string{
"http://example.com",
"http://example.org",
"http://example.net",
// 更多URL
}
const maxWorkers = 10
taskChan := make(chan string)
resultChan := make(chan string)
var wg sync.WaitGroup
// 创建工作池
for i := 0; i < maxWorkers; i++ {
wg.Add(1)
go worker(taskChan, resultChan, &wg)
}
// 提交任务
go func() {
for _, url := range urls {
taskChan <- url
}
close(taskChan)
}()
// 收集结果
go func() {
wg.Wait()
close(resultChan)
}()
// 输出结果
for result := range resultChan {
fmt.Println(result)
}
}
crawl
函数:模拟实际的网页爬取逻辑,返回爬取结果。
worker
函数:每个Goroutine执行的函数,从 taskChan
中获取URL,调用 crawl
函数爬取网页,并将结果发送到 resultChan
。
main
函数:
- 初始化URL列表。
- 创建任务通道
taskChan
和结果通道 resultChan
。
- 创建工作池,启动10个Goroutine作为工作者。
- 将任务发送到
taskChan
并关闭通道。
- 等待所有工作者完成任务后关闭
resultChan
。
- 从
resultChan
中读取并输出结果。