控制Goroutine数量
- 使用工作池模式:
- 创建一个带缓冲的通道作为任务队列,用于存储待抓取的URL。
- 使用固定数量的Goroutine作为工作者,它们从任务队列中取出URL并执行抓取任务。例如:
package main
import (
"fmt"
"sync"
)
const (
numWorkers = 100
taskQueueSize = 1000
)
func worker(id int, taskQueue <-chan string, wg *sync.WaitGroup) {
defer wg.Done()
for url := range taskQueue {
// 执行网页抓取任务
fmt.Printf("Worker %d is fetching %s\n", id, url)
}
}
func main() {
var wg sync.WaitGroup
taskQueue := make(chan string, taskQueueSize)
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(i, taskQueue, &wg)
}
// 向任务队列中添加URL
urls := []string{"url1", "url2", "url3"}
for _, url := range urls {
taskQueue <- url
}
close(taskQueue)
wg.Wait()
}
- 动态调整Goroutine数量:
- 根据系统资源(如CPU、内存利用率)动态调整工作池中的Goroutine数量。可以使用
runtime
包获取系统信息,如runtime.NumCPU()
获取CPU核心数,然后根据实际情况调整Goroutine数量。
高效复用连接资源
- 使用连接池:
- Go语言标准库中的
http
包提供了Transport
结构体,可以通过配置Transport
来实现连接池。例如:
package main
import (
"fmt"
"net/http"
)
func main() {
transport := &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 30 * time.Second,
}
client := &http.Client{Transport: transport}
// 使用client执行HTTP请求
resp, err := client.Get("http://example.com")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
}
- 连接复用策略:
- 确保同一个HTTP客户端实例在多个Goroutine之间复用,避免频繁创建和销毁连接。同时,设置合适的连接超时时间和最大空闲连接数,以平衡资源占用和性能。
处理网络故障
- 超时处理:
- 为每个HTTP请求设置超时时间,避免Goroutine因长时间等待响应而阻塞。例如:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Get("http://example.com")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
}
- 错误处理与资源释放:
- 当遇到网络故障(如连接超时、DNS解析失败等)时,及时返回错误并清理相关资源。例如,关闭HTTP响应体以释放连接资源。同时,可以将失败的任务重新放入任务队列进行重试,或者记录日志以便后续分析。
Go语言调度器对Goroutine调度的原理及性能影响
- 调度原理:
- Go语言调度器采用M:N调度模型,即多个Goroutine映射到多个操作系统线程上。调度器主要由
G-M-P
模型组成:
G
(Goroutine):代表一个轻量级的用户级线程,包含执行栈、指令指针等信息。
M
(Machine):代表一个操作系统线程,负责执行Goroutine。
P
(Processor):代表一个逻辑处理器,用于管理一组Goroutine的执行,它维护一个本地Goroutine队列。
- 调度器的工作流程:
- 当创建一个新的Goroutine时,它被放入某个
P
的本地队列中。
M
从P
的本地队列中取出Goroutine并执行。如果本地队列为空,M
会尝试从其他P
的队列中窃取任务(工作窃取算法)。
- 当Goroutine执行系统调用或被阻塞时,
M
会将其挂起,然后M
可以去执行其他Goroutine,直到被阻塞的Goroutine恢复执行。
- 性能影响:
- 优点:
- 高效的调度机制使得Go语言能够在高并发场景下充分利用多核CPU资源,提高程序的执行效率。
- 轻量级的Goroutine减少了线程创建和上下文切换的开销,使得可以轻松创建数以万计的Goroutine。
- 缺点:
- 如果Goroutine长时间执行计算密集型任务而不进行调度点(如系统调用、channel操作等),会导致其他Goroutine无法及时执行,影响整体性能。因此,在编写计算密集型的Goroutine时,应适当使用
runtime.Gosched()
主动让出CPU时间片。