Go标准库|sync.WaitGroup
风起于青萍之末,浪成于微澜之间 --- 《左传·宣公十五年》
sync.WaitGroup是Go标准库中的一个同步原语,用于等待一组goroutine完成其执行。它通过一个计数器来跟踪goroutine的状态,可以安全地在多个goroutine之间共享。
注: 当前版本为go1.18
sync.WaitGroup 的结构体定义如下:
type WaitGroup struct {
noCopy noCopy
state1 [3]uint32
}
noCopy: 这个字段用于保证WaitGroup不会被复制。Go 语言中有一个习惯用法,即通过嵌入noCopy字段来防止某些类型的值被复制。这是因为复制可能会导致状态的混乱,特别是在并发环境中。state1: 这个字段实际上是一个用来存储状态信息的数组。虽然这里定义了三个uint32类型的元素,但实际上sync.WaitGroup只使用其中的两个来存储等待的协程数量和信号量信息。
使用示例
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
// 启动 3 个协程
for i := 0; i < 3; i++ {
wg.Add(1) // 启动一个协程前调用 Add 方法
go func(i int) {
defer wg.Done() // 协程完成任务后调用 Done 方法
fmt.Printf("Task %d is running\n", i)
time.Sleep(2 * time.Second) // 模拟耗时操作
fmt.Printf("Task %d is done\n", i)
}(i)
}
wg.Wait() // 阻塞直到所有任务都完成
fmt.Println("All tasks are done")
}
这个例子中,主协程启动了 3 个协程,每个协程都休眠 2 秒。主协程使用 wg.Wait() 阻塞,直到所有协程都完成任务。
原理
sync.WaitGroup的核心是一个计数器,该计数器可以通过以下三个主要方法进行操作:
Add(delta int): 增加或减少计数器的值。正数表示增加计数器,负数表示减少计数器。Done(): 相当于Add(-1),用于表示一个goroutine已经完成。Wait(): 阻塞直到计数器的值变为0。
WaitGroup的零值是有效的(计数器为0),因此可以直接使用零值的WaitGroup而不需要初始化。当 Wait() 被调用时,如果计数器大于 0,则调用 Wait() 的协程将被阻塞。当计数器变为 0 时,所有被阻塞的协程将被唤醒,继续执行。
注意:
wg.Add(1)应该在启动协程之前调用。wg.Done()应该在协程任务完成后调用。wg.Wait()应该在主协程中调用,用于等待所有任务完成。
使用场景
WaitGroup常用于以下场景:
- 等待多个goroutine完成:在主goroutine中启动多个goroutine,并等待它们全部完成后再继续后续操作。
- 并行任务管理:当需要并行执行多个任务,并在所有任务完成后进行统一的结果处理或下一步操作时使用。
关于我
我叫赵辰安,野蛮生长的程序员,专注于后端开发,游戏服务端开发,乐于交各行各业的朋友,不管是闲聊还是需要帮助还是合作,欢迎你来私信我。