前言
前面的内存模型的章节里讲到 内存屏障
barrier 指令要求所有对内存的操作都必须要“扩散”到 memory 之后才能继续执行其他对 memory 的操作。
因此,我们可以用高级点的 atomic compare-and-swap,或者直接用更高级的锁,通常是标准库提供。
一、实现原理
实现模式
Barging., Handsoff, Spinning
Barging: 这种模式是为了提高吞吐量,当锁被释放时,它会唤醒第一个等待者,然后把锁给第一个等待者或者给第一个请求锁的人。 提升吞吐量
Handsoff:
当锁释放时候,锁会一直持有直到第一个等待者准备好获取锁。它降低了吞吐量,因为锁被持有,即使另一个 goroutine 准备获取它。
一个互斥锁的 handsoff 会完美地平衡两个goroutine 之间的锁分配,但是会降低性能,因为它会迫使第一个 goroutine 等待锁
Spinning:自旋在等待队列为空或者应用程序重度使用锁时效果不错。Parking 和 Unparking goroutines 有不低的性能成本开销,相比自旋来说要慢得多。
饥饿模式:Go 1.9 通过添加一个新的饥饿模式来解决下面例子中问题,该模式将会在释放时候触发 handsoff。所有等待锁超过一毫秒的 goroutine(也称为有界等待)将被诊断为饥饿。当被标记为饥饿状态时,unlock 方法会 handsoff 把锁直接扔给第一个等待者。 在饥饿模式下,自旋也被停用,因为传入的goroutines 将没有机会获取为下一个等待者保留的锁。
实现原理
先讲原理在讲源码。 只讲源码的都是耍流氓!
lock
调用lock方法时
1.当锁处于初始的状态会直接调用CAS的方法获取锁
Fast path: grab unlocked mutex.获取成功执行 race 检测
获取失败执行2
2.加锁失败会进入 lockSlow 函数
2.1 首先会判断是否可以进入自旋状态 如果可以进入自旋 最多自旋4次
2.2 自旋结束计算当前锁的状态
2.3 尝试根据CAS 获取锁 如果没有获取到就调用 runtime_SemacquireMutex 方法休眠当前 goroutine
2.4 休眠结束 goroutine 会被唤醒 会先判断当前是否处在饥饿状态
2.4.1 如果当前 goroutine 超过 1ms 都没有获取到锁就会进饥饿模式
设置唤醒和饥饿标记,重置迭代次数重新执行获取锁的循环
2.4.1 如果处于饥饿模式就获得互斥锁 如果当前等待队列 只有当前的 goroutine
就会退出等待队列
CAS 方法在这里指的是
atomic.CompareAndSwapInt32(addr, old, new) bool
方法,atomic
包是由golang提供的low-level的原子操作封装,主要用来解决进程同步为题,官方并不建议直接使用。CompareAndSwapInt32()
就是int32型数字的compare-and-swap
实现。cas(&addr, old, new)的意