Sync.Mutex
一、结构体
type Mutex struct {
state int32 // 互斥锁的状态:被g持有,空闲等
sema uint32 // 信号量,用于阻塞/唤醒 goroutine(协程)
}
//使用
var mtx sync.Mutex
mtx.Lock()
mtx.Unlock()
这里的 state 字段是 int32 类型,但是它被分为了4 部分操作,为了使用更少的内存去表达锁的各个状态。
二、正常模式和饥饿模式
在正常模式下自旋和排队同时存在的,执行Lock的goroutine会先一边自旋,尝试过几次后还没拿到锁,就需要去排队等待了。这种在排队之前先让大家来抢的模式,能够有更高的吞吐量,因为频繁的挂起、唤醒goroutine会带来较多的开销。但又不能无限制的自旋,要把自旋的开销控制在较小的范围内。所以在正常模式下,Mutex有更好的性能,但是可能会出现队列尾端的goroutine迟迟抢不到锁(尾端延迟)的情况。而饥饿模式下不在自旋尝试,所有goroutine都要排队,严格的先来后到,对于防止出现尾端延迟来讲特别重要。
- normal 模式下:
- 加入了竞争的情况,被唤醒的 g 可能会与刚到来的 g 一起竞争锁,但是被唤醒的 g 很可能失败。因为 g 被唤醒就说明锁已经被释放了,那么自旋的很可能已经获得锁了
- 睡眠时间超过 1ms 的 g,被唤醒后想要将 mutex 切换为 starving 模式,切换后也会再次进入阻塞队列且排在队列头部,等待锁的释放别唤醒
- starving 模式下:
- 只有被唤醒的等待者才能加锁,其他的 g 全都进入 FIFO 阻塞队列
谨记:在原子操作处,所有的 g 都会是串行化(序列化)的。
三、Mutex.Lock()
原子操作atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)
原子操作确保其它处理器或者核总是访问原子操作之后的最新的值;虽然有 cache 的存在,内存屏障解决,必须要要等到内存屏障管道中的未完成数据都刷新到内存中,再进行操作,而且此操作会让相关的 cpu 的缓存行失效(cpu 缓存一致性协议)。
四、Mutex.UnLock()
所以尽量做到谁 Lock 谁 UnLock,做到谁申请,就由谁释放。一般都是在同一个方法中使用。
Unlock 方法可以被任意的 goroutine 调用释放锁,即使是没持有这个互斥锁的 goroutine,也可以进行这个操作。这是因为,Mutex 本身并没有包含持有这把锁的 goroutine 的信息,所以,Unlock 也不会对此进行检查。
Sync.RWMutex
一、结构体
type RWMutex struct {
w Mutex // 互斥锁
writerSem uint32 // reader 完成后会释放 writer信号量
readerSem uint32 // writer 完成后会释放 reader 信号量
readerCount int32 // 当前的 reader 的数量(以及用来判断是否有 writer 线程请求锁或者持有锁)
readerWait int32 // writer 等待 reader 完成的数量,到 0,writer 会被唤醒
}
const rwmutexMaxReaders = 1 << 30 // 表识最大 reader 线程数量
//使用
RLock、RUnLock、Lock、UnLock
RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。
标签:writer,RWMutex,goroutine,Sync,互斥,Mutex,唤醒 From: https://www.cnblogs.com/ling11/p/16591632.html