文章目录
-
-
- 一、简介
- [二、WaitGroup 的使用](#二、WaitGroup 的使用)
-
- [1. 什么是 WaitGroup?](#1. 什么是 WaitGroup?)
- [2. 基本操作](#2. 基本操作)
- [3. WaitGroup 示例](#3. WaitGroup 示例)
- [4. 注意事项](#4. 注意事项)
- [三、Mutex 的使用](#三、Mutex 的使用)
-
- [1. 什么是 Mutex?](#1. 什么是 Mutex?)
- [2. 基本操作](#2. 基本操作)
- [3. Mutex 示例](#3. Mutex 示例)
- 四、竞争条件示例与解决
-
- [1. 竞争条件问题示例](#1. 竞争条件问题示例)
- [2. 使用 Mutex 解决竞争条件](#2. 使用 Mutex 解决竞争条件)
- [五、使用 RWMutex 实现读写锁](#五、使用 RWMutex 实现读写锁)
-
- [RWMutex 示例](#RWMutex 示例)
- 六、小结
-
一、简介
在 Golang 的并发编程中,sync
包提供了一些基本的同步原语,用于解决多个 Goroutine 之间的协调与资源共享问题。常用的工具包括:
- WaitGroup:用于等待一组 Goroutine 完成任务。
- Mutex:用于保护共享资源,避免多个 Goroutine 同时修改数据导致竞争条件(Race Condition)。
本篇博客将详细介绍 WaitGroup
和 Mutex
的用法,并通过示例帮助初学者理解它们在实际场景中的应用。
二、WaitGroup 的使用
1. 什么是 WaitGroup?
WaitGroup
是一个计数器,用于等待多个 Goroutine 完成任务。它可以让主 Goroutine 阻塞,直到所有子 Goroutine 完成工作。
2. 基本操作
- Add(delta int):添加 delta 个 Goroutine 到计数器。
- Done():每个 Goroutine 在完成任务后调用,减少计数器的值。
- Wait():阻塞调用者,直到计数器变为 0。
3. WaitGroup 示例
go
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 任务完成时调用 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 // 创建 WaitGroup 实例
for i := 1; i <= 3; i++ {
wg.Add(1) // 每启动一个 Goroutine,计数器加 1
go worker(i, &wg)
}
wg.Wait() // 等待所有 Goroutine 完成
fmt.Println("All workers done!")
}
输出:
Worker 1 starting...
Worker 2 starting...
Worker 3 starting...
Worker 1 done
Worker 2 done
Worker 3 done
All workers done!
4. 注意事项
- 确保 Done 与 Add 匹配 :每次
Add
对应一次Done
,否则可能会导致Wait
永远阻塞。 - 并发调用 Add :如果需要在运行时动态启动 Goroutine,应确保
Add
操作发生在Wait
之前。
三、Mutex 的使用
1. 什么是 Mutex?
Mutex (互斥锁)用于防止多个 Goroutine 同时访问共享资源,从而避免竞争条件。只有一个 Goroutine 能够在同一时间持有 Mutex,其它 Goroutine 必须等待。
2. 基本操作
- Lock():获取锁。如果锁已被其他 Goroutine 持有,则阻塞。
- Unlock():释放锁。
3. Mutex 示例
go
package main
import (
"fmt"
"sync"
"time"
)
var (
counter int // 共享变量
mutex sync.Mutex // 互斥锁保护共享变量
)
func increment(id int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
mutex.Lock() // 获取锁
counter++ // 修改共享变量
fmt.Printf("Goroutine %d incremented counter to %d\n", id, counter)
mutex.Unlock() // 释放锁
time.Sleep(time.Millisecond * 100) // 模拟其他操作
}
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go increment(i, &wg)
}
wg.Wait()
fmt.Println("Final Counter Value:", counter)
}
输出(示例):
Goroutine 1 incremented counter to 1
Goroutine 2 incremented counter to 2
Goroutine 3 incremented counter to 3
...
Final Counter Value: 15
四、竞争条件示例与解决
1. 竞争条件问题示例
如果不使用 Mutex,多个 Goroutine 同时修改共享变量时会产生不一致的数据。
go
package main
import (
"fmt"
"sync"
"time"
)
var counter int // 共享变量
func increment(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
counter++
time.Sleep(time.Millisecond * 100) // 模拟其他操作
}
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final Counter Value:", counter)
}
输出:
Final Counter Value: 12
由于多个 Goroutine 竞争访问 counter
,导致最终值不正确。
2. 使用 Mutex 解决竞争条件
go
mutex.Lock()
counter++
mutex.Unlock()
通过为每次修改操作加锁,确保同一时间只有一个 Goroutine 可以修改 counter
,从而避免数据不一致。
五、使用 RWMutex 实现读写锁
RWMutex 是一种读写锁,允许多个 Goroutine 同时读取,但在写入时需要独占锁。
RWMutex 示例
go
package main
import (
"fmt"
"sync"
"time"
)
var (
data = 0
rwMux sync.RWMutex
)
func readData(id int, wg *sync.WaitGroup) {
defer wg.Done()
rwMux.RLock() // 获取读锁
fmt.Printf("Goroutine %d reading data: %d\n", id, data)
rwMux.RUnlock() // 释放读锁
}
func writeData(wg *sync.WaitGroup) {
defer wg.Done()
rwMux.Lock() // 获取写锁
data++
fmt.Printf("Writing data: %d\n", data)
rwMux.Unlock() // 释放写锁
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go readData(i, &wg)
}
wg.Add(1)
go writeData(&wg)
wg.Wait()
}
六、小结
- WaitGroup 用于等待一组 Goroutine 完成任务,是 Goroutine 同步的常用工具。
- Mutex 和 RWMutex 用于保护共享资源,避免竞争条件。
- 使用锁时需注意避免死锁 和性能瓶颈。
在下一篇博客中,我们将介绍 Golang 的 Context 包,并探讨如何在并发操作中控制超时和取消任务。敬请期待!