You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用sync.WaitGroup实现Go Web爬虫出现死锁panic,正确惯用法是什么?

问题根因
  • 互斥锁未正常释放:你在Crawl函数中调用mutex.Lock()加锁后,当判断URL已经被爬取过的分支直接返回时,没有执行mutex.Unlock()释放锁,导致锁被永久占用,其他所有操作urlCache的goroutine都会陷入无限等待,最终触发所有goroutine休眠的死锁错误。
  • main函数逻辑顺序错误:你在执行wg.Wait()等待所有爬取协程结束之前,就打印了Main: Completed,属于逻辑顺序错误,虽不直接导致死锁,但不符合预期执行流程。
修复方案

核心问题修复

  1. 补全锁释放逻辑:可以直接在已爬取判断分支中新增解锁操作,更推荐的方式是加锁后立即用defer声明解锁,避免所有分支遗漏释放:
mutex.Lock()
defer mutex.Unlock() // 加锁后立即声明延迟解锁,所有返回分支都会自动执行解锁
if alreadyFetched := urlCache[url]; alreadyFetched {
    fmt.Printf("Crawl: %v already fetched\n", url)
    return
}
urlCache[url] = true
  1. 调整main函数执行顺序,先等待协程执行完成再打印完成日志:
func main() {
    var wg sync.WaitGroup

    fmt.Println("Main: Starting worker")
    wg.Add(1)
    go Crawl("https://golang.org/", 4, fetcher, &wg)
    fmt.Println("Main: Waiting for workers to finish")
    wg.Wait()
    fmt.Println("Main: Completed")
}

符合Go开发习惯的优化建议

  • 避免使用全局存储:可以将urlCache和mutex封装到自定义的爬取器结构体中,作为参数传递给Crawl函数,降低代码耦合,同时支持多个爬取任务并行执行互不干扰。
  • 可以进一步用channel代替sync.WaitGroup实现协程调度,更符合Go「通过通信共享内存」的设计哲学,不过针对当前练习场景,修复锁问题后使用WaitGroup的方案已经足够简洁可用。

内容的提问来源于stack exchange,提问作者Evgeni Petrov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 13:15:04