Go语言实战案例:使用WaitGroup等待多个协程完成

在并发编程中,经常需要在主协程中等待多个子协程执行完毕再进行下一步操作。Go 提供了一个非常实用的同步原语:sync.WaitGroup,可以帮助我们优雅地解决这个问题。


一、什么是 WaitGroup?

sync.WaitGroup 是 Go 标准库中的一个结构体,用于等待一组 goroutine 执行完成。

常用方法:

方法 功能说明
Add(n int) 设置需要等待的 goroutine 数量
Done() 每个 goroutine 执行完成后调用,计数器减 1
Wait() 阻塞当前 goroutine,直到计数器归零为止

二、为什么需要 WaitGroup?

在并发场景中,如果主函数先于子协程执行完毕,程序将会直接退出,导致协程未完成任务。例如:

go 复制代码
func main() {
    go func() {
        fmt.Println("Hello from goroutine")
    }()
    // 没有等待,主函数直接退出
}

输出往往为空或者输出不完整,根本原因是主 goroutine 没有等待子 goroutine。


三、实战案例:等待多个协程完成任务

示例:开启 5 个 goroutine,每个打印一条消息,主程序等待所有协程完成后再退出。

go 复制代码
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 执行完毕,通知 WaitGroup
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second) // 模拟耗时操作
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // 启动一个 goroutine 前调用 Add(1)
        go worker(i, &wg)
    }

    wg.Wait() // 阻塞直到所有 Done() 被调用
    fmt.Println("所有任务已完成")
}

输出示例:

bash 复制代码
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 2 done
Worker 4 done
Worker 1 done
Worker 3 done
Worker 5 done
所有任务已完成

输出顺序可能每次不同,因为 goroutine 是并发执行的。


四、使用建议与注意事项

✅ 正确使用方式:

  • Add(n) 必须在 启动 goroutine 之前 调用。
  • • 每个 goroutine 结束时必须调用 Done()
  • Wait() 通常放在主 goroutine 中,阻塞直到所有任务完成。

⚠️ 易犯错误:

    1. 忘记 Done()
    go 复制代码
    go func() {
        fmt.Println("hello")
        // 忘记 wg.Done(),会导致 wg.Wait() 永久阻塞
    }()
    1. 在 goroutine 内调用 Add()
    scss 复制代码
    go func() {
        wg.Add(1) // ❌不推荐:会出现数据竞争
        ...
    }()

    正确方式是主 goroutine 中统一 Add()。


五、封装使用:任务调度器

我们可以封装一个并发调度器,更加灵活:

scss 复制代码
type TaskScheduler struct {
    wg sync.WaitGroup
}

func (t *TaskScheduler) Run(task func()) {
    t.wg.Add(1)
    go func() {
        defer t.wg.Done()
        task()
    }()
}

func (t *TaskScheduler) Wait() {
    t.wg.Wait()
}

使用示例:

css 复制代码
func main() {
    scheduler := TaskScheduler{}

    for i := 0; i < 5; i++ {
        index := i
        scheduler.Run(func() {
            fmt.Printf("Task %d running\n", index)
            time.Sleep(500 * time.Millisecond)
        })
    }

    scheduler.Wait()
    fmt.Println("All scheduled tasks completed.")
}

六、与其他同步机制对比

同步机制 用途
sync.WaitGroup 等待一组协程结束
sync.Mutex 保护共享资源
sync.Once 只执行一次的操作(如初始化)
channel 通信为主,也可用于同步控制

七、结语

在并发控制中,sync.WaitGroup 是一种简单却非常强大的工具,能帮助我们控制 goroutine 的生命周期,避免程序提前退出或资源泄漏等问题。掌握其使用方法,是每位 Go 开发者的必备技能。

✅ 实战建议:在进行网络抓取、任务分发、并发计算等场景时,推荐优先考虑 WaitGroup 来管理协程。


相关推荐
覆东流19 分钟前
4.Java运算符与表达式
java·开发语言·后端
马丁玩编程39 分钟前
GitHub 3.5k Star 之后,Ragent AI 框架新版本来了
后端·面试·github
微石科技39 分钟前
医疗设备维保如何从被动维修转向预防管理?宁波微石科技让运行数据提前发信号
java·后端·struts
AINative软件工程40 分钟前
LLM 应用的 Warm-Up 工程实践:冷启动延迟从 12 秒砍到 800ms 的 5 个工程手段
后端·llm·ai编程
SomeB1oody44 分钟前
【RustyML入门】5.1. 回归指标
开发语言·后端·机器学习·rust·教程
卷无止境1 小时前
手写 SQL 在 Tortoise ORM 里到底能派上什么用场
后端·python·fastapi
再吃一根胡萝卜1 小时前
从 Docker 到 Kubernetes:微服务的“操作系统”长什么样?
后端
卷无止境1 小时前
FastAPI、Tortoise ORM 与 PostgreSQL 三件套 是否好用呢?
后端·python·fastapi
程序员爱钓鱼1 小时前
Rust Trait详解:定义共享行为与抽象接口
后端·面试·rust
再吃一根胡萝卜1 小时前
分布式事务:从“强一致”到“最终一致”,我为什么在微服务里放弃了 2PC?
后端