
本文详解 Go 并发编程中因循环变量复用导致对象被意外覆盖的经典问题,聚焦 for range 中取地址(&category)引发的指针指向同一内存位置的隐患,并提供安全、可复用的修复方案。
本文详解 go 并发编程中因循环变量复用导致对象被意外覆盖的经典问题,聚焦 `for range` 中取地址(`&category`)引发的指针指向同一内存位置的隐患,并提供安全、可复用的修复方案。
在 Go 的并发爬虫实践中,一个隐蔽却高频的问题是:多个 goroutine 接收到的结构体指针实际指向同一个栈变量地址,导致数据被覆盖或逻辑错乱。您提供的代码正是这一问题的典型体现——extractCategories 返回一个 *[]Category,随后通过 for _, category := range *categories 遍历并发送 &category 到 downloadChannel,看似合理,实则危险。
? 问题本质:range 循环变量是复用的
Go 规范明确规定:for range 中的迭代变量(如 category)在整个循环生命周期内只声明一次,每次迭代仅更新其值。这意味着:
for _, category := range *categories {
downloadChannel <- &category // ❌ 所有 goroutine 收到的都是 &category 的同一地址!
}无论 *categories 包含 10 个还是 100 个不同 Category 值,category 变量本身只有一个内存地址。当 goroutine 在后续执行中读取 *target(即 *Category)时,看到的已是最后一次迭代写入的值——这就是为何 parent 字段被“覆盖”,URL 拼接出现 travel-tourism/political-ideological-organizations/ 这类错误路径的根本原因。
该问题在多 worker 场景下被显著放大:goroutine 调度不可预测,category 值可能在 A goroutine 解引用前已被 B goroutine 覆盖。
✅ 正确解法:为每个元素创建独立变量副本
最直接、最安全的方式是显式创建每个元素的独立副本,再取其地址:
categoriesValues := *categories // 先解引用,获得切片副本
for i := range categoriesValues {
category := categoriesValues[i] // ✅ 每次迭代都新建局部变量
downloadChannel <- &category
}或者更简洁地(推荐):
for i := range *categories {
category := (*categories)[i] // 显式索引 + 局部赋值,语义清晰
downloadChannel <- &category
}⚠️ 注意:不要写成
downloadChannel —— 这仍可能触发编译器优化导致地址复用(尤其在某些 Go 版本中),<strong>显式赋值给新变量是 Go 社区公认的可靠模式</strong>。
?️ 进阶建议:避免不必要的指针传递
进一步提升健壮性,可重构为值传递 + 显式拷贝:
- 将
worker函数签名改为接收Category值而非*Category; - 在
main中直接发送category(值),worker 内部再取地址(若需修改); - 或者——更符合 Go 习惯——让
Category结构体保持小而轻量(当前完全满足),直接按值传递,消除指针歧义。
例如:
// 修改 worker 签名
func worker(downloadChannel <-chan Category, results chan<- DownloadResult) {
for target := range downloadChannel { // target 是值拷贝
doc, err := goquery.NewDocument(target.url)
if err != nil {
log.Fatal(err)
}
results <- DownloadResult{doc, &target} // 此处取地址是安全的(target 是局部变量)
}
}
// main 中发送值
for i := range *categories {
downloadChannel <- (*categories)[i] // 发送值,非指针
}? 总结:三条黄金准则
-
永远警惕
&v在for range中的使用:只要v是循环变量,&v就是危险的; -
需要指针?先复制再取址:
x := slice[i]; send(&x)是安全范式; - 优先考虑值语义:Go 的高效复制机制使小结构体按值传递既安全又高效,减少共享状态复杂度。
修复后,您的爬虫将稳定生成正确的层级 URL(如 political-ideological-organizations/ 不再错误挂载于 travel-tourism/ 下),并发扩展性与数据一致性同步提升。

















