Mutex 是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex 类型的锁和线程无关,可以由不同的线程加锁和解锁。

在一个goroutine获得 Mutex 后,其他goroutine只能等到这个goroutine释放该Mutex使用Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
在 Lock() 之前使用 Unlock() 会导致 panic 异常
已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁
在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
适用于读写不确定,并且只有一个读或者写的场景

package syncimport ("internal/race""sync/atomic""unsafe"
)
// 被定义在 runtime 包中,src/runtime/panic.go 的 sync_throw 方法
func throw(string) // provided by runtime // A Mutex is a mutual exclusion lock. mutex 是一个互斥锁
// The zero value for a Mutex is an unlocked mutex. 零值是没有被上锁的互斥锁。
// A Mutex must not be copied after first use. 首次使用后,不得复制互斥锁。
type Mutex struct {// 将一个32位整数拆分为// 当前阻塞的goroutine数目(29位)|饥饿状态(1位)|唤醒状态(1位)|锁状态(1位) 的形式,来简化字段设计state int32sema  uint32  // 信号量
}// A Locker represents an object that can be locked and unlocked.
type Locker interface {Lock()Unlock()
}const (//用最后一位表示当前对象锁的状态,0-未锁住 1-已锁住mutexLocked = 1 << iota // mutex is locked  1 表示是否被锁定  0001 //用倒数第二位表示当前对象是否被唤醒 0- 未唤醒 1-唤醒  //【注意: 未被唤醒并不是指休眠,而是指为了让所能被设置被唤醒的一个初始值】mutexWoken  //2 表示是否被唤醒  0010 //用倒数第三位表示当前对象是否为饥饿模式,0为正常模式,1为饥饿模式。mutexStarving  //4 表示是否饥饿   0100//3 表示 从倒数第四位往前的bit位表示在排队等待的goroutine数目(共对于 32位中占用 29 位)mutexWaiterShift = iota// Mutex fairness.//// Mutex can be in 2 modes of operations: normal and starvation.// In normal mode waiters are queued in FIFO order, but a woken up waiter// does not own the mutex and competes with new arriving goroutines over// the ownership. New arriving goroutines have an advantage -- they are// already running on CPU and there can be lots of them, so a woken up// waiter has good chances of losing. In such case it is queued at front// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,// it switches mutex to the starvation mode.//// In starvation mode ownership of the mutex is directly handed off from// the unlocking goroutine to the waiter at the front of the queue.// New arriving goroutines don't try to acquire the mutex even if it appears// to be unlocked, and don't try to spin. Instead they queue themselves at// the tail of the wait queue.//// If a waiter receives ownership of the mutex and sees that either// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,// it switches mutex back to normal operation mode.//// Normal mode has considerably better performance as a goroutine can acquire// a mutex several times in a row even if there are blocked waiters.// Starvation mode is important to prevent pathological cases of tail latency./** 互斥量可分为两种操作模式:正常和饥饿。【正常模式】,等待的goroutines按照FIFO(先进先出)顺序排队,但是goroutine被唤醒之后并不能立即得到mutex锁,它需要与新到达的goroutine争夺mutex锁。因为新到达的goroutine已经在CPU 上运行了,所以被唤醒的goroutine很大概率是争夺mutex锁是失败 的。出现这样的情况时候,被唤 醒goroutine需要排队在队列的前面。如果被唤醒的goroutine有超过1ms没有获取到mutex锁,那么 它就会变为饥饿模式。在饥饿模式中,mutex锁直接从解锁的goroutine交给队列前面的goroutine。 新 达到的goroutine也不会去争夺mutex锁(即使没有锁,也不能去自旋),而是到等待队列尾部排队。【饥饿模式】,锁的所有权将从unlock的gorutine直接交给交给等待队列中的第一个。新来的 goroutine将不会尝试去获得锁,即使锁看起来是unlock状态, 也不会去尝试自旋操作,而是放在等 待队列的尾部。如果有一个等待的goroutine获取到mutex锁了,如果它满足下条件中的任意一个, mutex将会切换回去正常模式:1. 是等待队列中的最后一个goroutine2. 它的等待时间不超过1ms。正常模式:有更好的性能,因为goroutine可以连续多次获得mutex锁;饥饿模式:能阻止尾部延迟的现象,对于预防队列尾部goroutine一致无法获取mutex锁的问题。*/starvationThresholdNs = 1e6  // 1ms
)// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
/*
如果锁已经在使用中,则调用goroutine 直到互斥锁可用为止。
在此之前我们必须先说下 四个重要的方法;
【runtime_canSpin】:在 src/runtime/proc.go 中被实现 sync_runtime_canSpin;表示 比较保守的自旋,golang中自旋锁并不会一直自旋下去,在runtime包中runtime_canSpin方法做了一些限制,传递过来的iter大等于4或者cpu核数小等于1,最大逻辑处理器大于1,至少有个本地的P队列,并且本地的P队列可运行G队列为空。
【runtime_doSpin】:在src/runtime/proc.go 中被实现 sync_runtime_doSpin;表示会调用procyield函数,该函数也是汇编语言实现。函数内部循环调用PAUSE指令。PAUSE指令什么都不做,但是会消耗CPU时间,在执行PAUSE指令时,CPU不会对它做不必要的优化。
【runtime_SemacquireMutex】:在 src/runtime/sema.go中被实现sync_runtime_SemacquireMutex;表示通过信号量 阻塞当前协程
【runtime_Semrelease】: 在src/runtime/sema.go 中被实现 sync_runtime_Semrelease
*/
func (m *Mutex) Lock() {// Fast path: grab unlocked mutex.// 如果m.state为0,说明当前的对象还没有被锁住,进行原子性赋值操作设置为mutexLocked状态,CompareAnSwapInt32返回true// 否则说明对象已被其他goroutine锁住,不会进行原子赋值操作设置,CopareAndSwapInt32返回false/**如果mutext的state没有被锁,也没有等待/唤醒的goroutine, 锁处于正常状态,那么获得锁,返回.比如锁第一次被goroutine请求时,就是这种状态。或者锁处于空闲的时候,也是这种状态*/if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {if race.Enabled {race.Acquire(unsafe.Pointer(m))}return}/** 在锁定没有成功的时候,才会往下面走 */// Slow path (outlined so that the fast path can be inlined)m.lockSlow()
}func (m *Mutex) lockSlow() {/**首先判断是否已经加锁并处于正常模式,将原先锁的state & (1 和 4 | 的结果,目的就是为了检验 state 是处于 1 还是 4 状态, 还                是两者都是.如果与1相等,则说明此时处于 正常模式并且已经加锁,而后判断当前协程是否可以自旋。如果可以自旋,则通过右移三位判断是否还有协程正在等待这个锁,如果有,并通过 低2位 判断是否该所处于被唤醒状态,如果并没有,则将其状态量设为被唤醒的状态,之后进行自旋,直到该协程自旋数量达到上限,或者当前锁被解锁,或者当前锁已经处于 饥饿模式*/// 标记本goroutine的等待时间   开始等待时间戳var waitStartTime int64 // 本goroutine是否已经处于饥饿状态 ,饥饿模式标识 true: 饥饿  false: 未饥饿starving := false // 本goroutine是否已唤醒 被唤醒标识  true: 被唤醒   flase: 未被唤醒awoke := falseiter := 0 // 自旋次数old := m.state / 保存当前对象锁状态,做对比用for {// Don't spin in starvation mode, ownership is handed off to waiters// so we won't be able to acquire the mutex anyway.// 不要在饥饿模式下自旋,将锁的控制权交给阻塞任务,否则无论如何当前goroutine都无法获得互斥锁。/**相当于xxxx...x0xx & 0101 = 01,当前对象锁被使用old & (是否锁定|是否饥饿) == 是否锁定runtime_canSpin() 表示 是否可以自旋。runtime_canSpin返回true,可以自旋。即: 判断当前goroutine是否可以进入自旋锁第一个条件:是state已被锁,但是不是饥饿状态。如果时饥饿状态,自旋时没有用的,锁的拥有权直接交给了等待队列的第一个。第二个条件:是还可以自旋,多核、压力不大并且在一定次数内可以自旋, 具体的条件可以参考`sync_runtime_canSpin`的实现。如果满足这两个条件,不断自旋来等待锁被释放、或者进入饥饿状态、或者不能再自旋。*/if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {// Active spinning makes sense.// Try to set mutexWoken flag to inform Unlock// to not wake other blocked goroutines.// 主动旋转是有意义的。试着设置 mutexWoken (锁唤醒)标志,告知解锁,不唤醒其他阻塞的goroutines。// old&mutexWoken == 0 再次确定是否被唤醒: xxxx...xx0x & 0010 = 0// old>>mutexWaiterShift != 0 查看是否有goroution在排队// tomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) 将对象锁在老状态上追加唤醒状态:xxxx...xx0x | 0010 = xxxx...xx1x// 如果当前标识位 awoke为 未被唤醒 && (old 也为 未被唤醒) && 有正在等待的 goroutine && 则修改 old 为 被唤醒// 且修改标识位 awoke 为 true 被唤醒/**自旋的过程中如果发现state还没有设置woken标识,则设置它的woken标识, 并标记自己为被唤醒。*/if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true // 更改标识位为 唤醒true}// 进入自旋锁后当前goroutine并不挂起,仍然在占用cpu资源,所以重试一定次数后,不会再进入自旋锁逻辑runtime_doSpin()iter++ // 累加自旋次数old = m.state // 保存mutex对象即将被设置成的状态continue}// 以下代码是不使用**自旋**的情况/**到了这一步, state的状态可能是:1. 锁还没有被释放,锁处于正常状态2. 锁还没有被释放, 锁处于饥饿状态3. 锁已经被释放, 锁处于正常状态4. 锁已经被释放, 锁处于饥饿状态并且本gorutine的 awoke可能是true, 也可能是false (其它goutine已经设置了state的woken标识)new 复制 state的当前状态, 用来设置新的状态old 是锁当前的状态*/new := old/** 下面的几个 if 分别是并列语句,来判断如何设置state的new状态 *//**如果old state状态不是饥饿状态, new state设置锁,尝试通过CAS获取锁,如果old state状态是饥饿状态, 则不设置new state的锁,因为饥饿状态下锁直接转给等待队列的第一个.*/// 不要试图获得饥饿goroutine的互斥锁,新来的goroutines必须排队。// 对象锁饥饿位被改变 为 1 ,说明处于饥饿模式// xxxx...x0xx & 0100 = 0xxxx...x0xx// Don't try to acquire starving mutex, new arriving goroutines must queue./**【一】如果是正常状态 (如果是正常,则可以竞争到锁) */if old&mutexStarving == 0 {// xxxx...x0xx | 0001 = xxxx...x0x1,将标识对象锁被锁住new |= mutexLocked}/** 【二】处于饥饿且锁被占用 状态下  */// xxxx...x1x1 & (0001 | 0100) => xxxx...x1x1 & 0101 != 0;当前mutex处于饥饿模式并且锁已被占用,新加入进来的goroutine放到队列后面,所以 等待者数目 +1if old&(mutexLocked|mutexStarving) != 0 {// 更新阻塞goroutine的数量,表示mutex的等待goroutine数目加1// 首先,如果此时还是由于别的协程的占用无法获得锁或者处于饥饿模式,都在其state加8表示有新的协程正在处于等待状态new += 1 << mutexWaiterShift}// The current goroutine switches mutex to starvation mode.// But if the mutex is currently unlocked, don't do the switch.// Unlock expects that starving mutex has waiters, which will not// be true in this case./**如果之前由于自旋而将该锁唤醒,那么此时将其低二位的状态量重置为0 (即 未被唤醒)。之后判断starving是否为true,如果为true说明在上一次的循环中,锁需要被定义为 饥饿模式,那么在这里就将相应的状态量低3位设置为1表示进入饥饿模式*//***【三】如果当前goroutine已经处于饥饿状态 (表示当前 goroutine 的饥饿标识位 starving), 并且old state的已被加锁,将new state的状态标记为饥饿状态, 将锁转变为饥饿状态.*/// 当前的goroutine将互斥锁转换为饥饿模式。但是,如果互斥锁当前没有解锁,就不要打开开关,设置mutex状态为饥饿模式。Unlock预期有饥饿的goroutine// old&mutexLocked != 0  xxxx...xxx1 & 0001 != 0;锁已经被占用// 如果 饥饿且已被锁定if starving && old&mutexLocked != 0 {new |= mutexStarving // 【追加】饥饿状态}/**【四】如果本goroutine已经设置为唤醒状态, 需要清除new state的唤醒标记, 因为本goroutine要么获得了锁,要么进入休眠,总之state的新状态不再是woken状态.*/// 如果 goroutine已经被唤醒,因此需要在两种情况下重设标志if awoke {// The goroutine has been woken from sleep,// so we need to reset the flag in either case.// xxxx...xx0x & 0010 == 0,如果唤醒标志为与awoke的值不相协调就panic// 即 state 为 未被唤醒if new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}// new & (^mutexWoken) => xxxx...xxxx & (^0010) => xxxx...xxxx & 1101 = xxxx...xx0x// 设置唤醒状态位0,被未唤醒【只是为了,下次被可被设置为i被唤醒的初识化标识,而不是指休眠】new &^= mutexWoken}/**之后尝试通过cas将new的state状态量赋值给state,如果失败,则重新获得其state在下一步循环重新重复上述的操作。如果成功,首先判断已经阻塞时间(通过标记本goroutine的等待时间waitStartTime ),如果为零,则从现在开始记录*/// 将新的状态赋值给state,注意new的锁标记不一定是true, 也可能只是标记一下锁的state是饥饿状态if atomic.CompareAndSwapInt32(&m.state, old, new) {/**如果old state的状态是未被锁状态,并且锁不处于饥饿状态,那么当前goroutine已经获取了锁的拥有权,返回*/// xxxx...x0x0 & 0101 = 0,表示可以获取对象锁 (即还是判断之前的状态,锁不是饥饿 也不是被被锁定,锁已经可用了)if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// If we were already waiting before, queue at the front of the queue.// 以下的操作都是为了判断是否从【饥饿模式】中恢复为【正常模式】// 判断处于FIFO还是LIFO模式// 如果等待时间不为0 那么就是 LIFO// 在正常模式下,等待的goroutines按照FIFO(先进先出)顺序排队//  设置/计算本goroutine的等待时间queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime() // 更新等待时间}// 通过runtime_SemacquireMutex()通过信号量将当前协程阻塞// 函数 runtime_SemacquireMutex 定义在 sema.go/**既然未能获取到锁, 那么就使用 [sleep原语] 阻塞本goroutine如果是新来的goroutine,queueLifo=false, 加入到等待队列的尾部,耐心等待如果是唤醒的goroutine, queueLifo=true, 加入到等待队列的头部*/runtime_SemacquireMutex(&m.sema, queueLifo, 1)// 当之前调用 runtime_SemacquireMutex 方法将当前新进来争夺锁的协程挂起后,如果协程被唤醒,那么就会继续下面的流程// 如果当前 饥饿状态标识为饥饿||当前时间-开始等待时间>1ms 则 都切换为饥饿状态标识//使用 [sleep原语] 之后,此goroutine被唤醒,计算当前goroutine是否已经处于饥饿状态.starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs// 刷新下 中转变量/** 得到当前的锁状态 */old = m.state/**如果当前的state已经是饥饿状态那么锁应该处于Unlock状态,那么应该是锁被直接交给了本goroutine*/if old&mutexStarving != 0 {// If this goroutine was woken and mutex is in starvation mode,// ownership was handed off to us but mutex is in somewhat// inconsistent state: mutexLocked is not set and we are still// accounted as waiter. Fix that./**如果当前的state已被锁,或者已标记为唤醒, 或者等待的队列中为空,那么state是一个非法状态*/// xxxx...xx11 & 0011 != 0 又可能是被锁定,又可能是被唤醒 或者 没有等待的goroutineif old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}// delta 表示当前状态下的等待数// 否则下一次的循环中将该锁设置为 饥饿模式。// 如果已经是这个模式,那么就会将 状态量的等待数 减1/**当前goroutine用来设置锁,并将等待的goroutine数减1.lock状态 -一个gorotine数,表示状态 delta == (lock + (减去一个等待goroutine数))*/delta := int32(mutexLocked - 1<<mutexWaiterShift)// 并判断当前如果已经没有等待的协程,就没有必要继续维持饥饿模式,同时也没必要继续执行该循环(当前只有一个协程在占用锁)/**如果本goroutine并不处于饥饿状态,或者它是最后一个等待者,那么我们需要把锁的state状态设置为正常模式.*/if !starving || old>>mutexWaiterShift == 1 {// Exit starvation mode.// Critical to do it here and consider wait time.// Starvation mode is so inefficient, that two goroutines// can go lock-step infinitely once they switch mutex// to starvation mode.// 退出饥饿模式。// 在这里做到并考虑等待时间至关重要。// 饥饿模式是如此低效,一旦将互斥锁切换到饥饿模式,两个goroutine就可以无限锁定。delta -= mutexStarving}// 设置新state, 因为已经获得了锁,退出、返回atomic.AddInt32(&m.state, delta)break}// 修改为本goroutine 是否被唤醒标识位/**如果当前的锁是正常模式,本goroutine被唤醒,自旋次数清零,从for循环开始处重新开始*/awoke = trueiter = 0} else {// 如果CAS不成功,重新获取锁的state, 从for循环开始处重新开始 继续上述动作old = m.state}}if race.Enabled {race.Acquire(unsafe.Pointer(m))}
}// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
//
// A locked Mutex is not associated with a particular goroutine.
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
// 解锁一个未被锁定的互斥锁时,是会报错
// 锁定的互斥锁与特定的goroutine无关。
// 允许一个goroutine锁定Mutex然后
// 安排另一个goroutine解锁它。
func (m *Mutex) Unlock() {if race.Enabled {_ = m.staterace.Release(unsafe.Pointer(m))}// Fast path: drop lock bit./** 如果state不是处于锁的状态, 那么就是Unlock根本没有加锁的mutex, panic  */// state -1 标识解锁 (移除锁定标记)new := atomic.AddInt32(&m.state, -mutexLocked)if new != 0 {// Outlined slow path to allow inlining the fast path.// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.m.unlockSlow(new)}
}func (m *Mutex) unlockSlow(new int32) {/**释放了锁,还得需要通知其它等待者被通知的 goroutine 会去做下面的事情锁如果处于饥饿状态,直接交给等待队列的第一个, 唤醒它,让它去获取锁锁如果处于正常状态,则需要唤醒对头的goroutine 让它和新来的goroutine去竞争锁,当然极大几率为失败,这时候 被唤醒的goroutine需要排队在队列的前面 (然后自旋)。如果被唤醒的goroutine有超过1ms没有获取到mutex锁,那么它就会变为饥饿模式*/// 再次校验下 标识,new state如果是正常状态, 验证锁状态是否符合if (new+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}// xxxx...x0xx & 0100 = 0 ;判断是否处于正常模式if new&mutexStarving == 0 {old := new // 记录缓存值for {// If there are no waiters or a goroutine has already// been woken or grabbed the lock, no need to wake anyone.// In starvation mode ownership is directly handed off from unlocking// goroutine to the next waiter. We are not part of this chain,// since we did not observe mutexStarving when we unlocked the mutex above.// So get off the way.// 如果没有等待的goroutine或goroutine不处于空闲,则无需唤醒任何人// 在饥饿模式下,锁的所有权直接从解锁goroutine交给下一个 正在等待的goroutine (等待队列中的第一个)。// 注意: old&(mutexLocked|mutexWoken|mutexStarving) 中,因为在最上面已经 -mutexLocked 并且进入了 if new&mutexStarving == 0// 说明目前 只有在还有goroutine 或者 被唤醒的情况下才会 old&(mutexLocked|mutexWoken|mutexStarving) != 0// 即:当休眠队列内的等待计数为 0  或者 是正常但是 处于被唤醒或者被锁定状态,退出// old&(mutexLocked|mutexWoken|mutexStarving) != 0     xxxx...x0xx & (0001 | 0010 | 0100) => xxxx...x0xx & 0111 != 0/**如果没有等待的goroutine, 或者锁不处于空闲的状态,直接返回.*/if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}// Grab the right to wake someone.// 减少等待goroutine个数,并添加 唤醒标识new = (old - 1<<mutexWaiterShift) | mutexWoken/** 设置新的state, 这里通过信号量去唤醒一个阻塞的goroutine去获取锁. */if atomic.CompareAndSwapInt32(&m.state, old, new) {// 释放锁,发送释放信号 (解除 阻塞信号量)runtime_Semrelease(&m.sema, false, 1)return}// 赋值给中转变量,然后启动下一轮old = m.state}} else {// Starving mode: handoff mutex ownership to the next waiter, and yield// our time slice so that the next waiter can start to run immediately.// Note: mutexLocked is not set, the waiter will set it after wakeup.// But mutex is still considered locked if mutexStarving is set,// so new coming goroutines won't acquire it./**饥饿模式下:直接将锁的拥有权传给等待队列中的第一个.注意:此时state的mutexLocked还没有加锁,唤醒的goroutine会设置它。在此期间,如果有新的goroutine来请求锁, 因为mutex处于饥饿状态, mutex还是被认为处于锁状态,新来的goroutine不会把锁抢过去.*/runtime_Semrelease(&m.sema, true, 1)}
}

Mutex锁分为正常模式饥饿模式。一开始默认处于正常模式。在正常模式中,每个新加入竞争锁行列的协程都会直接参与到锁的竞争当中来,而处于饥饿模式时,所有新进入的协程都会直接被放入等待队列中挂起,直到其所在队列之前的协程全部执行完毕。

在正常模式中协程的挂起等待时间如果大于某个值,就会进入饥饿模式
其中,state用来保存mutex的状态量低一位表示是否上锁低二位表示当前锁对象是否被唤醒低三位表示该锁是否处于饥饿状态,而其余位表示当前正被该锁阻塞的协程数。而sema则是作为信号量来作为阻塞的依据

上述 32位的整数映射到state 字段上的情景为:

state: |32|31|...| |3|2|1|\__________/ | | || | | || | | mutex的占用状态(1被占用,0可用)| | || | mutex的当前goroutine是否被唤醒| || 饥饿位,0正常,1饥饿|等待唤醒以尝试锁定的goroutine的计数,0表示没有等待者

上述很的运算都是位运算,原因是:锁在同一时刻可能具备多个状态,还有一个原因就是state字段 只有 低位的三位是用来控制状态的,而其他的位都是用来做计数的,所以不能直接赋值操作,而是用了位运算赋值

正常模式】,等待的goroutines按照 FIFO(先进先出)顺序排队,但是goroutine被唤醒之后并不能立即得到mutex锁,它需要与新到达的goroutine争夺mutex锁。 因为新到达的goroutine已经在CPU上运行了,所以被唤醒的goroutine很大概率是争夺mutex锁是失败的。出现这样的情况时候,被唤醒的goroutine需要排队在队列的前面。 如果被唤醒的goroutine有超过1ms没有获取到mutex锁,那么它就会变为 饥饿模式。 在饥饿模式中,mutex锁直接从解锁的goroutine交给队列前面的goroutine。新达到的goroutine也不会去争夺mutex锁(即使没有锁,也不能去自旋),而是到等待队列尾部排队。

饥饿模式】,有一个goroutine获取到mutex锁了,如果它满足下条件中的任意一个,mutex将会切换回去正常模式: 1. 是等待队列中的最后一个goroutine 。2. 它的等待时间不超过1ms。 正常模式有更好的性能,因为goroutine可以连续多次获得mutex锁; 饥饿模式需要预防队列尾部goroutine一直无法获取mutex锁的问题。

尝试获取mutex的goroutine也有状态,有可能它是新来的goroutine也有可能是被唤醒的goroutine可能是处于正常状态的goroutine也有可能是处于饥饿状态的goroutine

Lock ()

Unlock ()

参考 https://blog.csdn.net/qq_25870633/article/details/83448234

go学习笔记 sync/mutex源码相关推荐

  1. go学习笔记 sync/Cond源码

    Cond需要指定一个Locker,通常是一个*Mutex或*RWMutex. func (c *Cond) Broadcast() 和 func (c *Cond) Signal() 唤醒因wait ...

  2. JUC.Condition学习笔记[附详细源码解析]

    JUC.Condition学习笔记[附详细源码解析] 目录 Condition的概念 大体实现流程 I.初始化状态 II.await()操作 III.signal()操作 3个主要方法 Conditi ...

  3. K8s基础知识学习笔记及部分源码剖析

    K8s基础知识学习笔记及部分源码剖析 在学习b站黑马k8s视频资料的基础上,查阅了配套基础知识笔记和源码剖析,仅作个人学习和回顾使用. 参考资料: 概念 | Kubernetes 四层.七层负载均衡的 ...

  4. The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译

    前言 源码编译是重头戏,这节笔记记录如何使用 make 命令编译相关部件.由于部分包在墙外,带来了一点麻烦,还分享一个 replace 方式来翻墙的办法. 小能手这段时间在学习 The Things ...

  5. Netty学习笔记 - 1 (带源码分析部分)

    2021年12月 北京 xxd 一.Netty是什么 Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github 上的独立项目. Netty 是一个异步的.基于事件驱动的网络应用 ...

  6. The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译 - 190821

    文章目录 前言 1 依赖包替换 2 编译准备 3 编译 3.1 cli 编译 3.2 stack 编译 3.3 前端编译 END 前言 源码编译是重头戏,这节笔记记录如何使用 make 命令编译相关部 ...

  7. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  8. 【从线性回归到 卷积神经网络CNN 循环神经网络RNN Pytorch 学习笔记 目录整合 源码解读 B站刘二大人 绪论(0/10)】

    深度学习 Pytorch 学习笔记 目录整合 数学推导与源码详解 B站刘二大人 目录传送门: 线性模型 Linear-Model 数学原理分析以及源码详解 深度学习 Pytorch笔记 B站刘二大人( ...

  9. jMetal学习笔记(二)-NSGAii源码解读

    前言 上篇笔记根据使用手册介绍了jMetal的架构,但是由于使用手册撰写时间太早(最近更新时间是08年),现在jmetal框架更新了,所以很多都已经不适用,这篇笔记会穿插讲解jmetal架构知识. 其 ...

最新文章

  1. Windows日志及其保护
  2. python中怎么做分组问题_详解Python中的分组函数groupby和itertools)
  3. 如何减小与“大牛”的差距
  4. 年龄到底怎么算才对_如意甘霖vs嘉和保,到底谁才是最佳男性重疾险
  5. AAAI 2020 | 微软亚洲研究院6篇精选论文在家看
  6. 网络规划设计师复习笔记
  7. Linux操作系统下三种配置环境变量的方法
  8. shell中返回值是1为真还是假_shell脚本返回值问题的解决
  9. 【转】复制网页内容三招
  10. PTA 乙级 【1005】继续(3n+1)猜想
  11. HttpPrinter-网页打印控件
  12. Qt的对话框与窗口--标准的对话框
  13. php函数形参,PHP中的函数形参的默认值
  14. linux下shell命令之hwclock
  15. Python——永久存储:腌制一缸美味的泡菜
  16. 五、SQL–索引/约束⑥(外键约束)
  17. 整体格局:国企、民营、外资各自竞优几何
  18. C#控件之OpenFileDialog选择图片文件并在picturebox里面显示
  19. Windows下使用GitHub Pages搭建hexo博客详细教程以及Next主题超全配置
  20. 新零售模式,数据管理更轻松

热门文章

  1. 云呐|事业单位固定资产管理解决方案
  2. gamma软件linux安装图示,linux下安装GAMMA软件
  3. 流水线设计的方法和作用
  4. JQ实现ajax请求网抑云数据+art-template渲染
  5. 这些学习用品如何用俄语表达
  6. Web 服务器的搭建
  7. ajax实例里面的函数,AJAX实例:用callback函数的AJAX实例
  8. babyos (四)—— SVGA、VBE基础与切换到髙分辨率模式
  9. matlab mcc m,急!!!在线等 matlab 关于mcc
  10. 吃饱没事做之———用windows自带的Hyper-v 创建的Linux虚拟机来和主机的windows系统互联。