Go语言中WaitGroup并发同步的实现

 更新时间:2026年04月03日 09:59:32   作者:王码码2035哦  
本文主要介绍了Go语言中WaitGroup并发同步的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

写了十几年代码的Go后端老兵。今天聊聊WaitGroup,这个并发同步的小能手。

一、为什么需要WaitGroup

上周我们重构了一个数据导入功能,需要并发处理多个文件。但程序总是提前退出,有些文件还没处理完就结束了。

这就是没有做好并发同步的后果。WaitGroup可以帮我们等待所有goroutine完成。

二、WaitGroup的基本用法

1. 基础示例

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1) // 增加计数器
        go func(id int) {
            defer wg.Done() // 减少计数器
            fmt.Printf("Worker %d starting\n", id)
            time.Sleep(time.Second)
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }
    wg.Wait() // 等待所有goroutine完成
    fmt.Println("All workers done")
}

2. 错误处理

func processFiles(files []string) error {
    var wg sync.WaitGroup
    errChan := make(chan error, len(files))
    for _, file := range files {
        wg.Add(1)
        go func(f string) {
            defer wg.Done()
            if err := processFile(f); err != nil {
                errChan <- err
            }
        }(file)
    }
    wg.Wait()
    close(errChan)
    for err := range errChan {
        if err != nil {
            return err
        }
    }
    return nil
}

三、WaitGroup的实战技巧

1. 批量处理

func batchProcess(items []Item, batchSize int) {
    var wg sync.WaitGroup
    semaphore := make(chan struct{}, batchSize) // 限制并发数
    for _, item := range items {
        wg.Add(1)
        semaphore <- struct{}{} // 获取信号量
        go func(i Item) {
            defer wg.Done()
            defer func() { <-semaphore }() // 释放信号量
            process(i)
        }(item)
    }
    wg.Wait()
}

2. 超时控制

func processWithTimeout(items []Item, timeout time.Duration) error {
    var wg sync.WaitGroup
    done := make(chan struct{})
    go func() {
        for _, item := range items {
            wg.Add(1)
            go func(i Item) {
                defer wg.Done()
                process(i)
            }(item)
        }
        wg.Wait()
        close(done)
    }()
    select {
    case <-done:
        return nil
    case <-time.After(timeout):
        return fmt.Errorf("timeout")
    }
}

3. 动态添加任务

func crawler(urls []string) {
    var wg sync.WaitGroup
    urlChan := make(chan string, 100)
    // 启动固定数量的worker
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for url := range urlChan {
                newUrls := fetch(url)
                for _, u := range newUrls {
                    urlChan <- u // 动态添加新URL
                }
            }
        }()
    }
    // 发送初始URL
    for _, url := range urls {
        urlChan <- url
    }
    close(urlChan)
    wg.Wait()
}

四、WaitGroup的常见陷阱

1. Add和Done不匹配

// 错误:在goroutine内部调用Add
for i := 0; i < 5; i++ {
    go func() {
        wg.Add(1) // 错误!可能还没Add就Wait了
        defer wg.Done()
        // ...
    }()
}
wg.Wait()
// 正确:在启动goroutine之前Add
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // ...
    }()
}
wg.Wait()

2. 复制WaitGroup

// 错误:WaitGroup被复制
func wrong(wg sync.WaitGroup) { // 值传递,复制了WaitGroup
    wg.Done()
}
// 正确:传递指针
func right(wg *sync.WaitGroup) {
    wg.Done()
}

3. 重复调用Done

// 错误:可能调用多次Done
func bad() {
    wg.Add(1)
    go func() {
        defer wg.Done()
        if err := doSomething(); err != nil {
            wg.Done() // 重复调用!
            return
        }
    }()
}

// 正确:只调用一次
func good() {
    wg.Add(1)
    go func() {
        defer wg.Done()
        doSomething()
    }()
}

五、WaitGroup vs Channel

场景推荐方案
等待多个goroutine完成WaitGroup
传递数据Channel
控制并发数Channel(信号量)
错误处理Channel + WaitGroup

六、性能优化

1. 避免过度并发

// 不好的做法:无限制并发
for _, item := range items {
    wg.Add(1)
    go func(i Item) {
        defer wg.Done()
        process(i)
    }(item)
}
// 好的做法:限制并发数
semaphore := make(chan struct{}, 10)
for _, item := range items {
    wg.Add(1)
    semaphore <- struct{}{}
    go func(i Item) {
        defer wg.Done()
        defer func() { <-semaphore }()
        process(i)
    }(item)
}

2. 复用goroutine

// 使用worker pool
func workerPool(jobs []Job, workers int) {
    var wg sync.WaitGroup
    jobChan := make(chan Job, len(jobs))
    
    // 启动固定数量的worker
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobChan {
                process(job)
            }
        }()
    }
    
    // 发送任务
    for _, job := range jobs {
        jobChan <- job
    }
    close(jobChan)
    
    wg.Wait()
}

七、总结

WaitGroup是并发编程的必备工具,用好它可以:

  • 优雅地等待多个goroutine完成
  • 实现复杂的并发控制逻辑
  • 提高程序的性能和稳定性

记住:能跑就行,别折腾。但该用WaitGroup的时候,一定要用对。

到此这篇关于Go语言中WaitGroup并发同步的实现的文章就介绍到这了,更多相关Go语言 WaitGroup并发同步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang多模块开发的详细过程

    Golang多模块开发的详细过程

    这篇文章主要给大家介绍了关于Golang多模块开发的详细过程,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2023-02-02
  • GO开发编辑器安装图文详解

    GO开发编辑器安装图文详解

    这篇文章主要介绍了GO开发编辑器安装,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Go单元测试利器testify使用示例详解

    Go单元测试利器testify使用示例详解

    这篇文章主要为大家介绍了Go单元测试利器testify使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Win7环境下搭建Go开发环境(基于VSCode编辑器)

    Win7环境下搭建Go开发环境(基于VSCode编辑器)

    这篇文章主要介绍了Win7环境下搭建Go开发环境(基于VSCode编辑器),需要的朋友可以参考下
    2017-02-02
  • go函数的参数设置默认值的方法

    go函数的参数设置默认值的方法

    Go语言不直接支持函数参数默认值,但可以通过指针、结构体、变长参数和选项模式等方法模拟,下面给大家分享几种方式模拟函数参数的默认值功能,感兴趣的朋友一起看看吧
    2025-01-01
  • 如何使用go实现创建WebSocket服务器

    如何使用go实现创建WebSocket服务器

    文章介绍了如何使用Go语言和gorilla/websocket库创建一个简单的WebSocket服务器,并实现商品信息的实时广播,感兴趣的朋友一起看看吧
    2024-11-11
  • Go日志框架zap增强及源码解读

    Go日志框架zap增强及源码解读

    这篇文章主要为大家介绍了Go日志框架zap增强及源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • 详解Go语言中的作用域和变量隐藏

    详解Go语言中的作用域和变量隐藏

    这篇文章主要为大家介绍了Go语言中的作用域和变量隐藏,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,感兴趣的小伙伴可以了解一下
    2022-04-04
  • go中make用法及常见的一些坑

    go中make用法及常见的一些坑

    golang分配内存主要有内置函数new和make,下面这篇文章主要给大家介绍了关于go中make用法及常见的一些坑,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • Go语言基础切片的创建及初始化示例详解

    Go语言基础切片的创建及初始化示例详解

    这篇文章主要为大家介绍了Go语言基础切片的创建及初始化示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2021-11-11

最新评论