sync包
·
sync包
在 Go 语言中,sync 包提供了基本的同步原语(synchronization primitives),用于控制并发执行的 goroutine 之间的执行顺序和共享资源的访问。它包含了一些常用的同步工具,如互斥锁(Mutex)、读写锁(RWMutex)、等待组(WaitGroup)、条件变量(Cond)等。
1. sync.Mutex(互斥锁)
sync.Mutex 是最基本的互斥锁,用于保护共享资源,防止多个 goroutine 同时访问导致数据竞争(data race)
基本用法
import "sync"
var (
counter int
mu sync.Mutex
)
func increment() {
mu.Lock() // 加锁
counter++ // 临界区代码
mu.Unlock() // 解锁
}
Lock():获取锁,如果锁已被其他 goroutine 持有,则当前 goroutine 会阻塞,直到锁被释放。Unlock():释放锁,允许其他 goroutine 获取锁。
注意事项
- 必须成对使用:
Lock()和Unlock()必须配对,否则可能导致死锁或资源泄漏。 - 避免重复解锁:
Unlock()一个未加锁的Mutex会导致 panic。
2. sync.RWMutex(读写锁)
sync.RWMutex 是一种更高效的锁,适用于 读多写少 的场景。它允许多个 goroutine 同时读取数据,但写入时必须独占锁。
基本用法
var (
data map[string]string
rwmu sync.RWMutex
)
func readData(key string) string {
rwmu.RLock() // 读锁
defer rwmu.RUnlock() // 释放读锁
return data[key]
}
func writeData(key, value string) {
rwmu.Lock() // 写锁
defer rwmu.Unlock() // 释放写锁
data[key] = value
}
RLock()/RUnlock():读锁,允许多个 goroutine 同时读取。Lock()/Unlock():写锁,同一时间只允许一个 goroutine 写入。
适用场景
- 缓存系统:多个 goroutine 读取缓存,但写入缓存时需要独占锁。
- 配置管理:多个 goroutine 读取配置,但更新配置时需要加写锁。
3. sync.WaitGroup(等待组)
sync.WaitGroup 用于等待一组 goroutine 完成执行,常用于并发任务的管理。
基本用法
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // 增加等待的 goroutine 数量
go func(i int) {
defer wg.Done() // 完成后减少计数
fmt.Println(i)
}(i)
}
wg.Wait() // 等待所有 goroutine 完成
Add(delta int):增加等待的 goroutine 数量(delta可以是正数或负数)。Done():相当于Add(-1),表示一个 goroutine 完成。Wait():阻塞当前 goroutine,直到所有Add的计数归零。
注意事项
Add必须在Wait之前调用,否则可能导致Wait提前返回。Done必须调用,否则Wait会一直阻塞(可能导致死锁)。
4. sync.Once(单次执行)
sync.Once 确保某个操作 只执行一次,即使有多个 goroutine 调用。
基本用法
var once sync.Once
func initResource() {
once.Do(func() {
fmt.Println("Resource initialized (only once)")
})
}
// 多个 goroutine 调用 initResource,但初始化代码只会执行一次
Do(f func()):传入的函数f保证只会执行一次,即使多个 goroutine 同时调用Do。
适用场景
- 单例模式:确保全局变量只初始化一次。
- 延迟初始化:某些资源(如数据库连接)只需要初始化一次。
5. sync.Cond(条件变量)
sync.Cond 用于在多个 goroutine 之间进行 条件等待和通知,通常与 Mutex 或 RWMutex 配合使用。
基本用法
var (
mu sync.Mutex
cond = sync.NewCond(&mu)
ready bool
)
func worker() {
mu.Lock()
for !ready { // 等待条件满足
cond.Wait() // 释放锁并阻塞,被唤醒后重新获取锁
}
fmt.Println("Worker: ready!")
mu.Unlock()
}
func main() {
go worker()
time.Sleep(time.Second) // 模拟初始化
mu.Lock()
ready = true
cond.Signal() // 唤醒一个等待的 goroutine
// cond.Broadcast() // 唤醒所有等待的 goroutine
mu.Unlock()
}
Wait():释放锁并阻塞,直到被Signal()或Broadcast()唤醒。Signal():唤醒 一个 等待的 goroutine。Broadcast():唤醒 所有 等待的 goroutine。
适用场景
- 生产者-消费者模型:当队列为空时,消费者等待;生产者生产数据后通知消费者。
- 线程池任务调度:任务队列为空时,工作线程等待;有新任务时唤醒线程。
6. sync.Pool(对象池)
sync.Pool 用于缓存临时对象,减少内存分配和 GC 压力。
基本用法
var pool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024) // 创建新对象
},
}
func main() {
buf := pool.Get().([]byte) // 从池中获取对象
defer pool.Put(buf) // 使用完后放回池中
// 使用 buf...
}
Get():从池中获取一个对象(如果池为空,则调用New创建新对象)。Put(obj interface{}):将对象放回池中,供后续复用。
适用场景
- 频繁创建和销毁的对象(如
[]byte、bytes.Buffer)。 - 减少 GC 压力(避免频繁分配内存)。
注意事项
- 池中的对象可能被 GC 回收:
sync.Pool不保证对象长期存在,适合短期复用。 - 不适合存储重要数据:因为对象可能被其他 goroutine 获取并修改。
总结
| 同步原语 | 用途 | 特点 |
|---|---|---|
sync.Mutex | 互斥锁 | 保护共享资源,防止并发访问 |
sync.RWMutex | 读写锁 | 读多写少场景,提高并发性能 |
sync.WaitGroup | 等待组 | 等待多个 goroutine 完成 |
sync.Once | 单次执行 | 确保操作只执行一次 |
sync.Cond | 条件变量 | 协调 goroutine 间的条件等待 |
sync.Pool | 对象池 | 缓存临时对象,减少内存分配 |
这些同步工具是 Go 并发编程的核心,合理使用可以避免数据竞争、死锁等问题,提高程序的性能和可靠性。
更多推荐
所有评论(0)