Golang 中的 WaitGroup 如何使用,面试官经常考你

开发 开发工具
sync.WaitGroup 是 Go 语言标准库中提供的一个并发控制工具,用于等待一组 goroutine 完成执行。它本质上是一个计数器,可以跟踪正在运行的 goroutine 数量。

什么是 WaitGroup

sync.WaitGroup 是 Go 语言标准库中提供的一个并发控制工具,用于等待一组 goroutine 完成执行。它本质上是一个计数器,可以跟踪正在运行的 goroutine 数量。

WaitGroup 有三个主要方法:

Add(delta int)

 - 增加或减少等待的 goroutine 数量

Done()

 - 相当于 Add(-1),表示一个 goroutine 已完成

Wait()

 - 阻塞直到计数器归零

基本用法

package main

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

func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // goroutine 完成时计数器减1

	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 计数器加1
go worker(i, &wg)
}

	wg.Wait() // 等待所有 goroutine 完成
	fmt.Println("All workers completed")
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

WaitGroup 的常见误用

1. 在 goroutine 内部调用 Add

// 错误用法
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
go func() {
		wg.Add(1) // 可能在 Wait 之后才执行
defer wg.Done()
// 工作代码
}()
}
wg.Wait()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

正确做法:在启动 goroutine 前调用 Add

2. Add 和 Wait 的调用顺序问题

// 错误用法
var wg sync.WaitGroup
wg.Wait() // 可能直接通过,没有等待
wg.Add(1)
go func() {
defer wg.Done()
// 工作代码
}()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

正确做法:确保所有 Add 调用在 Wait 之前完成

3. 传递 WaitGroup 值而非指针

// 错误用法
func worker(wg sync.WaitGroup) {
defer wg.Done()
// 工作代码
}

var wg sync.WaitGroup
wg.Add(1)
go worker(wg) // 值传递,Done 不影响原始 WaitGroup
wg.Wait()     // 永远等待
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

正确做法:传递 WaitGroup 指针

4. 计数器归零后重用

var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
}()
wg.Wait()

wg.Add(1) // 错误: 在 Wait 之后再次使用
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

正确做法:WaitGroup 不能安全地重用,需要新建一个

WaitGroup + Channel 组合

结合 WaitGroup 和 Channel 可以实现更复杂的并发控制模式。

1. 限制并发数量

func main() {
var wg sync.WaitGroup
	ch := make(chan struct{}, 3) // 限制并发数为3

for i := 0; i < 10; i++ {
		wg.Add(1)
		ch <- struct{}{} // 获取令牌

go func(i int) {
defer wg.Done()
defer func() { <-ch }() // 释放令牌

			fmt.Printf("Worker %d starting\n", i)
			time.Sleep(time.Second)
			fmt.Printf("Worker %d done\n", i)
}(i)
}

	wg.Wait()
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

2. 收集 goroutine 结果

func worker(id int, wg *sync.WaitGroup, resChan chan<- string) {
defer wg.Done()

// 模拟工作
	time.Sleep(time.Second)
	resChan <- fmt.Sprintf("result from worker %d", id)
}

func main() {
var wg sync.WaitGroup
	resChan := make(chan string, 10)

// 启动多个 worker
for i := 0; i < 5; i++ {
		wg.Add(1)
go worker(i, &wg, resChan)
}

// 等待所有 worker 完成
go func() {
		wg.Wait()
close(resChan) // 所有结果收集完毕后关闭 channel
}()

// 处理结果
for res := range resChan {
		fmt.Println(res)
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

高级用法:动态调整并发数

func dynamicWorker(id int, wg *sync.WaitGroup, sem chan struct{}) {
defer wg.Done()

	fmt.Printf("Worker %d started\n", id)
	time.Sleep(time.Second * 2)
	fmt.Printf("Worker %d finished\n", id)

<-sem // 释放信号量
}

func main() {
var wg sync.WaitGroup
	maxConcurrent := 3
	sem := make(chan struct{}, maxConcurrent)

for i := 0; i < 10; i++ {
		sem <- struct{}{} // 获取信号量
		wg.Add(1)
go dynamicWorker(i, &wg, sem)
}

	wg.Wait()
	fmt.Println("All workers completed")
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

总结

WaitGroup 是 Go 中管理 goroutine 生命周期的强大工具,但需要注意:

  • 总是使用指针传递 WaitGroup
  • 在启动 goroutine 前调用 Add
  • 不要重用 WaitGroup
  • 结合 Channel 可以实现更精细的并发控制

通过合理使用 WaitGroup 和 Channel 的组合,可以构建出高效且安全的并发程序。

责任编辑:武晓燕 来源: Go语言圈
相关推荐

2015-08-13 10:29:12

面试面试官

2024-09-14 10:50:29

通信代码函数

2010-08-12 16:28:35

面试官

2023-02-16 08:10:40

死锁线程

2021-05-19 06:07:21

CSS 斜线效果技巧

2024-10-15 10:00:06

2024-02-20 14:10:55

系统缓存冗余

2024-03-18 14:06:00

停机Spring服务器

2023-06-05 09:23:00

Golang同步工具

2024-04-03 00:00:00

Redis集群代码

2021-07-06 07:08:18

管控数据数仓

2024-09-11 22:51:19

线程通讯Object

2025-03-17 00:00:00

2023-11-20 10:09:59

2020-09-30 06:49:25

MySQL查询删除

2015-08-24 09:00:36

面试面试官

2024-02-04 10:08:34

2020-08-06 07:49:57

List元素集合

2024-12-25 15:44:15

2024-01-26 13:16:00

RabbitMQ延迟队列docker
点赞
收藏

51CTO技术栈公众号