http.Get 默认卡住是因为 http.DefaultClient 的 Transport 未设超时,DNS失败、服务器无响应等场景会无限等待;安全并发需用带超时和连接池的复用 client、sync.WaitGroup 和 context.WithTimeout。

直接结论:用 http.Get + goroutine + sync.WaitGroup 就能快速实现并发网页状态检查,但不加超时控制或连接复用会卡死、泄漏、误判。
为什么 http.Get 默认会卡住?
Go 的 http.Get 使用默认的 http.DefaultClient,其底层 Transport 没设超时,遇到 DNS 不通、服务器无响应、TLS 握手卡顿等情况时,会无限等待(常见于内网地址或已下线域名)。这不是 bug,是设计使然——它把超时决策权交给你。
- 必须显式设置
Timeout、KeepAlive和TLSHandshakeTimeout - 不设
MaxIdleConnsPerHost时,并发量大可能耗尽本地端口(报错dial tcp: lookup xxx: no such host或too many open files) - 错误示例:
http.Get("http://example-down.com")可能阻塞 30 秒以上,拖垮整个检查流程
如何安全启动 100 个并发检查?
不能裸写 go http.Get(...) —— 缺少等待机制会导致主 goroutine 提前退出,检查还没开始就结束了;不带上下文则无法统一取消。
- 用
sync.WaitGroup记录待完成数量,defer wg.Done()放在函数末尾确保计数准确 - 用
context.WithTimeout(ctx, 5*time.Second)包裹每个请求,避免单个失败拖累全体 - 别在循环里反复 new
http.Client,复用一个带定制Transport的 client 更省资源 - 示例片段:
client := &http.Client{ Timeout: 5 * time.Second, Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 30 * time.Second, }, }
status code 是 200 就代表网页“可用”吗?
不是。很多 CDN、WAF 或反爬服务会返回 200 但内容为空、含跳转 JS、或返回 “Under Maintenance” 页面。真要判断“可访问”,得结合更多信号:
立即学习“go语言免费学习笔记(深入)”;
- 检查
resp.StatusCode是否在 2xx 范围(http.StatusOK等),排除 3xx 重定向和 4xx/5xx 错误 - 读取少量 body(如前 1024 字节),确认非空且不含典型错误关键词(
"404 Not Found"、"blocked") - 验证
Content-Type头是否含text/html或application/json,排除纯图片或 PDF 响应 - 注意:不要调用
ioutil.ReadAll(resp.Body),大页面会吃光内存;用io.LimitReader控制读取上限
容易被忽略的边界点
实际跑起来才发现的问题,往往不在主逻辑里:
- HTTP 重定向(301/302)默认会被
http.Client自动跟随,可能绕过你本想检测的目标地址;需设CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } - URL 必须带协议(
http://或https://),漏写会触发parse "example.com": first path segment in URL cannot contain colon - 并发数 > 1000 时,Linux 默认
ulimit -n(文件描述符限制)可能不够,需提前调高,否则大量socket: too many open files - HTTPS 网站若用自签名证书,需在
Transport.TLSClientConfig.InsecureSkipVerify = true—— 仅限测试环境



















