Go-WaitGroup、Mutex 和 Sync 包的使用

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 能够访问被保护的数据,避免数据竞争。

  • Lock:加锁。
  • Unlock:解锁。

示例代码

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 保证了 IncrementValue 方法在并发访问时的安全性。


3. sync 包中的其他常用工具

除了 WaitGroupMutexsync 包中还提供了 OnceRWMutex 等工具。

  • 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 管理并发任务

以下示例展示了如何使用 WaitGroupMutex 来同步多个任务的执行。

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 可以实现高效的读写分离。