1. WaitGroup 的基本用法
WaitGroup
是 Go 中用于等待一组 Goroutine 完成的同步工具。使用 Add
方法设置等待的 Goroutine 数量,Done
方法在
Goroutine 完成时调用,Wait
方法会阻塞直到所有 Goroutine 执行完成。
示例代码
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
| package main
import ( "fmt" "sync" "time" )
func worker(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d starting ", id) time.Sleep(time.Second) fmt.Printf("Worker %d done ", id) }
func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go worker(i, &wg) } wg.Wait() fmt.Println("All workers done") }
|
在这个例子中,main
函数会等待所有 worker
完成后才会退出。
2. Mutex 互斥锁的使用
Mutex
是用于保护共享数据的锁机制,确保同时只有一个 Goroutine 能够访问被保护的数据,避免数据竞争。
示例代码
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 30 31 32 33 34 35 36 37 38
| package main
import ( "fmt" "sync" )
type Counter struct { mu sync.Mutex count int }
func (c *Counter) Increment() { c.mu.Lock() c.count++ c.mu.Unlock() }
func (c *Counter) Value() int { c.mu.Lock() defer c.mu.Unlock() return c.count }
func main() { var wg sync.WaitGroup counter := Counter{}
for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Println("Counter:", counter.Value()) }
|
在这个例子中,Mutex
保证了 Increment
和 Value
方法在并发访问时的安全性。
3. sync 包中的其他常用工具
除了 WaitGroup
和 Mutex
,sync
包中还提供了 Once
和 RWMutex
等工具。
- sync.Once:保证只执行一次操作。
- sync.RWMutex:读写锁,可以允许多个读,但只允许一个写。
sync.Once 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main
import ( "fmt" "sync" )
var once sync.Once
func initialize() { fmt.Println("Initialization done") }
func main() { for i := 0; i < 5; i++ { go func() { once.Do(initialize) }() } }
|
4. 实际应用示例
使用 WaitGroup 和 Mutex 管理并发任务
以下示例展示了如何使用 WaitGroup
和 Mutex
来同步多个任务的执行。
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 30 31 32 33 34 35 36 37 38 39 40
| package main
import ( "fmt" "sync" "time" )
type SafeMap struct { mu sync.Mutex m map[string]int }
func (s *SafeMap) Write(key string, value int) { s.mu.Lock() s.m[key] = value s.mu.Unlock() }
func (s *SafeMap) Read(key string) int { s.mu.Lock() defer s.mu.Unlock() return s.m[key] }
func main() { sm := SafeMap{m: make(map[string]int)} var wg sync.WaitGroup
for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() sm.Write(fmt.Sprintf("key%d", id), id) time.Sleep(time.Millisecond * 100) fmt.Println("Read:", sm.Read(fmt.Sprintf("key%d", id))) }(i) } wg.Wait() }
|
5. 总结与最佳实践
- 合理使用 WaitGroup:适用于需要等待多个 Goroutine 完成的场景,避免 Goroutine 泄露。
- 锁的粒度:加锁时要保证最小粒度,减少对共享资源的锁定时间,以提高并发效率。
- 使用 sync 包工具简化并发代码:如 sync.Once 可以在初始化时使用,RWMutex 可以实现高效的读写分离。