sync #
- sync.Mutex
- sync.RWMutex
- sync.WaitGroup
- sync.Once
- sync.Cond
锁的类型 #
Mutex 互斥锁 #
type Mutex struct {
state int32
sema uint32 // semaphore 信号量
}
- sync.Mutex.Lock 和 sync.Mutex.Unlock
state #
# 8 位
**** ****
***** * * *
waitersCount mutexStarving mutexWoken mutexLocked
- mutexLocked — 表示互斥锁的锁定状态
- mutexWoken — 表示从正常模式被从唤醒
- mutexStarving — 当前的互斥锁进入饥饿状态
- Goroutine 超过
1ms
没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止被饿死,造成高尾延时
- Goroutine 超过
- waitersCount — 当前互斥锁上等待的 Goroutine 个数 (最多 128 个)
RWMutex #
type RWMutex struct {
w Mutex
writerSem uint32
readerSem uint32
readerCount int32
readerWait int32
}
- 写操作使用 sync.RWMutex.Lock 和 sync.RWMutex.Unlock 方法;
- 读操作使用 sync.RWMutex.RLock 和 sync.RWMutex.RUnlock 方法;
- 调用
sync.RWMutex.Lock
尝试获取写锁时;- 每次
sync.RWMutex.RUnlock
都会将 readerWait 其减一,当它归零时该 Goroutine 就会获得写锁; - 将 readerCount 减少 rwmutexMaxReaders 个数以阻塞后续的读操作;
- 每次
- 调用
sync.RWMutex.Unlock
释放写锁时,会先通知所有的读操作,然后才会释放持有的互斥锁;
读写互斥锁在互斥锁之上提供了额外的更细粒度的控制,能够在读操作远远多于写操作时提升性能。
WaitGroup #
type WaitGroup struct {
noCopy noCopy // wg 无法复制
state1 [3]uint32
}
requests := []*Request{...}
wg := &sync.WaitGroup{}
wg.Add(len(requests))
for _, request := range requests {
go func(r *Request) {
defer wg.Done()
// res, err := service.call(r)
}(request)
}
wg.Wait()
Done #
sync.WaitGroup.Done 只是对 sync.WaitGroup.Add 方法的简单封装, 我们可以向 sync.WaitGroup.Add 方法传入任意负数(需要保证计数器非负)快速将计数器归零以唤醒其他等待的 Goroutine;
Once #
- sync.Once.Do 方法中传入的函数只会被执行一次,哪怕函数中发生了
panic
; - 两次调用 sync.Once.Do 方法传入不同的函数也只会执行第一次调用的函数;
type Once struct {
done uint32
m Mutex
}
Cond #
type Cond struct {
L Locker // 用于保护内部的 notify 字段
notify notifyList
noCopy noCopy // 保证结构体不会在编译期间拷贝
checker copyChecker // 禁止运行期间发生的拷贝
}
type notifyList struct {
wait uint32
notify uint32
lock mutex
head *sudog
tail *sudog
}
- sync.Cond.Wait 将当前 Goroutine 陷入休眠状态
- 在调用之前一定要使用获取互斥锁,否则会触发程序崩溃
- sync.Cond.Signal 唤醒队列最前面的 Goroutine
- sync.Cond.Broadcast 唤醒队列中全部的 Goroutine
Mutex #
Mutex 互斥锁 #
- 在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个 goroutine 释放该 Mutex
- 使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
- 在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
- 在 Lock() 之前使用 Unlock() 会导致 panic 异常
- 已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁
- 适用于读写不确定,并且只有一个读或者写的场景
type Mutex struct {
// contains filtered or unexported fields
}
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
RWMutex 读写锁(读多写少) #
RWMutex 基于 Mutex 实现
- RWMutex 是单写多读锁,该锁可以加一个写锁或者多个读锁
- 写锁会阻止其他 goroutine(无论读和写)进来,整个锁由该 goroutine 独占
- Lock() 加写锁,Unlock() 解写锁
- 在 Lock() 之前使用 Unlock() 会导致 panic 异常
- 如果在加写锁之前已经有其他的读锁和写锁,则 Lock() 会阻塞直到该锁可用,为确保该锁可用,已经阻塞的 Lock() 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定
- 读锁占用的情况下会阻止写,不会阻止读,多个 goroutine 可以同时获取读锁
- RLock() 加读锁,RUnlock() 解读锁
- RLock() 加读锁时,如果存在写锁,则无法加读锁
- 在没有读锁的情况下调用 RUnlock() 会导致 panic 错误
- RUnlock() 的个数不得多余 RLock(),否则会导致 panic 错误
- 适用于读多写少的场景
Cond #
type Cond struct {
// L is held while observing or changing the condition
L Locker
// contains filtered or unexported fields
}
// Broadcast 会唤醒所有等待 c 的 goroutine
func (c *Cond) Broadcast()
// Signal 只唤醒 1 个等待 c 的 goroutine
func (c *Cond) Signal()
// Wait() 会自动释放 c.L,并挂起调用者的 goroutine。之后恢复执行,Wait() 会在返回时对 c.L 加锁。
// 除非被 Signal 或者 Broadcast 唤醒,否则 Wait() 不会返回
func (c *Cond) Wait()
- 条件
- 一个条件一定要有一个信号
- 信号
- wait 等待的是信号
- signal 发送的是信号
- 一个信号可以对应到多个条件
sync.Cond
vs sync.Mutex
#
- Mutex
- one goroutine for each write and read
- Cond
- multiple readers wait for the shared resources to be available
叶王 © 2013-2024 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。