今天,我们将继续探讨Go语言中的两个重要的同步工具:sync.WaitGroup 和 sync.Once。
sync.WaitGroup
sync.WaitGroup 是Go语言中的一种计数信号量,用于等待一组 goroutine 完成。它常用于等待一组并发任务全部完成后再继续执行。
使用方法
- 声明一个 sync.WaitGroup 类型的变量。
- 在每个 goroutine 启动之前调用 Add 方法,增加等待计数。
- 在每个 goroutine 完成时调用 Done 方法,减少等待计数。
- 在主 goroutine 中调用 Wait 方法,阻塞直到所有 goroutine 完成。
示例代码
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
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)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
在这个例子中,main 函数启动了5个 goroutine,每个 goroutine 都会运行 worker 函数。每个 worker 在完成时调用 wg.Done(),而 main 函数会等待所有 worker 完成后再继续执行。
注意事项
- WaitGroup 的计数器不能设为负数,否则会引发 panic。
- 必须确保在所有 Done 调用之前已经调用了 Add。
sync.Once
sync.Once 是一个用于确保某些操作只执行一次的结构体。它提供了一种线程安全的方式来执行一次性初始化操作。
使用方法
- 声明一个 sync.Once 类型的变量。
- 使用 Do 方法执行需要仅执行一次的操作。
示例代码
package main
import (
"fmt"
"sync"
)
func initialize() {
fmt.Println("Initializing...")
}
func main() {
var once sync.Once
for i := 0; i < 10; i++ {
go func(i int) {
once.Do(initialize)
fmt.Printf("Goroutine %d\n", i)
}(i)
}
// 等待所有 goroutine 完成
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
once.Do(initialize)
}()
}
wg.Wait()
}
在这个例子中,initialize 函数只会被执行一次,尽管有多个 goroutine 尝试调用 once.Do(initialize)。
注意事项
- sync.Once 的 Do 方法接受一个无参函数。
- 即使 Do 方法被多次调用,传入的函数也只会执行一次。
结合使用示例
我们可以结合 sync.WaitGroup 和 sync.Once,来完成一个更复杂的并发任务。假设我们有一个初始化操作,只需执行一次,但在多个 goroutine 中执行其他任务。
示例代码
package main
import (
"fmt"
"sync"
"time"
)
var (
once sync.Once
wg sync.WaitGroup
)
func initialize() {
fmt.Println("Initializing...")
time.Sleep(2 * time.Second) // 模拟初始化耗时
fmt.Println("Initialization complete")
}
func worker(id int) {
defer wg.Done()
once.Do(initialize)
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // 模拟工作
fmt.Printf("Worker %d done\n", id)
}
func main() {
const numWorkers = 5
wg.Add(numWorkers)
for i := 1; i <= numWorkers; i++ {
go worker(i)
}
wg.Wait()
fmt.Println("All workers done")
}
在这个例子中,initialize 函数只会执行一次,而 worker 函数会并发执行,等待所有 worker 完成后,程序才会继续执行。
总结
通过本文,我们了解了Go语言中的两个重要同步工具:sync.WaitGroup 和 sync.Once。sync.WaitGroup 用于等待一组 goroutine 完成,而 sync.Once 则确保某些操作只执行一次。这两个工具在实际开发中非常实用,能有效地帮助我们处理并发任务。