0

0

如何正确使用 Go 语言中的 sync.WaitGroup 避免程序永不退出

心靈之曲

心靈之曲

发布时间:2026-01-07 23:17:02

|

845人浏览过

|

来源于php中文网

原创

如何正确使用 Go 语言中的 sync.WaitGroup 避免程序永不退出

本文详解 `sync.waitgroup` 常见误用导致程序卡在 `wg.wait()` 不返回的问题,重点说明值传递 vs 指针传递、`defer wg.done()` 的调用时机等关键陷阱,并提供可立即修复的代码示例。

在 Go 并发编程中,sync.WaitGroup 是协调多个 goroutine 完成等待的经典工具。但若使用不当,极易引发程序“假死”——看似所有任务已完成,wg.Wait() 却永不返回。你提供的代码正是典型反例,问题根源在于两个关键错误:

? 错误一:WaitGroup 被值传递(copy),导致 Done() 失效

函数 downloadFromURL(url string, wg sync.WaitGroup) 的第二个参数是 值类型,Go 会复制整个 WaitGroup 结构体传入。后续在 goroutine 中调用 wg.Done(),实际操作的是副本,对 main 中原始 wg 的计数器 零影响。因此 wg.Wait() 永远等待未完成的 goroutine。

✅ 正确做法:必须传递指针

go downloadFromURL(url, &wg) // 传地址

并同步更新函数签名:

飞象老师
飞象老师

猿辅导推出的AI教学辅助工具

下载
func downloadFromURL(url string, wg *sync.WaitGroup) error {
    // ...
}

? 错误二:defer wg.Done() 放置位置不当

当前代码中 defer wg.Done() 写在函数末尾(return nil 之前),看似合理,实则危险:一旦函数因错误提前 return(如文件创建失败、HTTP 请求异常),defer 将被跳过,Done() 永不执行,WaitGroup 计数器无法归零。

✅ 正确做法:defer wg.Done() 应置于函数最开始
它应是 goroutine 启动后立即注册的“收尾承诺”,确保无论函数以何种路径退出,计数器必减一:

func downloadFromURL(url string, wg *sync.WaitGroup) error {
    defer wg.Done() // ✅ 第一行就声明:我结束时必调用 Done()

    tokens := strings.Split(url, "/")
    fileName := tokens[len(tokens)-1]
    fmt.Printf("Downloading %v to %v \n", url, fileName)

    content, err := os.Create("temp_docs/" + fileName)
    if err != nil {
        fmt.Printf("Error while creating %v because of %v\n", fileName, err)
        return err // 此处 return → defer wg.Done() 仍会执行
    }
    defer content.Close() // 别忘了关闭文件!

    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("Could not fetch %v because %v\n", url, err)
        return err // 同样,defer wg.Done() 保证执行
    }
    defer resp.Body.Close()

    _, err = io.Copy(content, resp.Body)
    if err != nil {
        fmt.Printf("Error while saving %v from %v\n", fileName, url)
        return err
    }

    fmt.Printf("Download complete for %v \n", fileName)
    return nil
}

⚠️ 其他重要注意事项

  • wg.Add(1) 必须在 go 语句前调用:你的代码已正确(wg.Add(1) 在 go downloadFromURL(...) 之前),这是安全的;若放在 goroutine 内部,则存在竞态风险。
  • 避免重复 Add/Over-add:确保每个 go 启动的 goroutine 对应且仅对应一次 Add(1)。
  • WaitGroup 不可复制或重用:一旦 Wait() 返回,该 WaitGroup 实例不应再被 Add() 或再次 Wait();如需复用,请重新声明变量。
  • 调试技巧:sync.WaitGroup 本身不提供公开 API 查询当前计数,但可通过 unsafe 或 reflect(不推荐生产环境)窥探;更实用的方法是添加日志:在 Add(1) 和 Done() 处打印计数变化,或使用 go tool trace 分析 goroutine 生命周期。

修复后的完整 main 函数逻辑清晰、健壮可靠:

func main() {
    links := parseLinks()
    var wg sync.WaitGroup

    for _, url := range links {
        if isExcelDocument(url) {
            wg.Add(1)
            go downloadFromURL(url, &wg) // ✅ 传指针
        } else {
            fmt.Printf("Skipping: %v\n", url)
        }
    }
    wg.Wait() // ✅ 现在能正确返回
    fmt.Println("All downloads completed.")
}

遵循这两条铁律——指针传递 WaitGroup + defer Done() 置顶——即可彻底规避 WaitGroup 永不完成的陷阱,写出稳定、可维护的并发 Go 程序。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

930

2023.08.02

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

429

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

201

2025.07.04

java值传递和引用传递有什么区别
java值传递和引用传递有什么区别

java值传递和引用传递的区别:1、基本数据类型的传递;2、对象的传递;3、修改引用指向的情况。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

109

2024.02.23

http500解决方法
http500解决方法

http500解决方法有检查服务器日志、检查代码错误、检查服务器配置、检查文件和目录权限、检查资源不足、更新软件版本、重启服务器或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

486

2023.11.09

http请求415错误怎么解决
http请求415错误怎么解决

解决方法:1、检查请求头中的Content-Type;2、检查请求体中的数据格式;3、使用适当的编码格式;4、使用适当的请求方法;5、检查服务器端的支持情况。更多http请求415错误怎么解决的相关内容,可以阅读下面的文章。

448

2023.11.14

HTTP 503错误解决方法
HTTP 503错误解决方法

HTTP 503错误表示服务器暂时无法处理请求。想了解更多http错误代码的相关内容,可以阅读本专题下面的文章。

3319

2024.03.12

http与https有哪些区别
http与https有哪些区别

http与https的区别:1、协议安全性;2、连接方式;3、证书管理;4、连接状态;5、端口号;6、资源消耗;7、兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2800

2024.08.16

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

32

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Excel 教程
Excel 教程

共162课时 | 20万人学习

成为PHP架构师-自制PHP框架
成为PHP架构师-自制PHP框架

共28课时 | 2.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号