GO互斥鎖sync.Mutex(2)

Mutext兩種模式

正常模式饑餓模式蟀淮。一開始默認(rèn)處于正常模式最住。在正常模式中,每個(gè)新加入競(jìng)爭(zhēng)鎖行列的協(xié)程都會(huì)直接參與到鎖的競(jìng)爭(zhēng)當(dāng)中來怠惶,而處于饑餓模式時(shí)涨缚,所有所有新進(jìn)入的協(xié)程都會(huì)直接被放入等待隊(duì)列中掛起,直到其所在隊(duì)列之前的協(xié)程全部執(zhí)行完畢策治。在正常模式中協(xié)程的掛起等待時(shí)間如果大于某個(gè)值脓魏,就會(huì)進(jìn)入饑餓模式。

type Mutex struct {
   state int32
   sema  uint32
}

其中览妖,state用來保存mutex的狀態(tài)量轧拄,低一位表示是否上鎖,低二位表示當(dāng)前鎖對(duì)象是否被喚醒讽膏,低三位表示該鎖是否處于饑餓狀態(tài),而其余位表示當(dāng)前正被該鎖阻塞的協(xié)程數(shù)拄丰。而sema則是作為信號(hào)量來作為阻塞的依據(jù)府树。

state: |32|31|...| |3|2|1|
 
         \__________/ | | |
              | | | |
              | | | mutex的占用狀態(tài)(1被占用俐末,0可用)
 
              | | |
              | | mutex的當(dāng)前goroutine是否被喚醒
              | |
              | 饑餓位,0正常奄侠,1饑餓
 
              |
               等待喚醒以嘗試鎖定的goroutine的計(jì)數(shù)卓箫,0表示沒有等待者

上述很多的運(yùn)算都是位運(yùn)算,原因是:鎖在同一時(shí)刻可能具備多個(gè)狀態(tài)垄潮,還有一個(gè)原因就是state字段 只有 低位的三位是用來控制狀態(tài)的烹卒,而其他的位都是用來做計(jì)數(shù)的,所以不能直接賦值操作弯洗,而是用了位運(yùn)算賦值旅急。

正常模式

等待的goroutines按照 FIFO(先進(jìn)先出)順序排隊(duì),但是goroutine被喚醒之后并不能立即得到mutex鎖牡整,它需要與新到達(dá)的goroutine爭(zhēng)奪mutex鎖藐吮。 因?yàn)樾碌竭_(dá)的goroutine已經(jīng)在CPU上運(yùn)行了,所以被喚醒的goroutine很大概率是爭(zhēng)奪mutex鎖是失敗的逃贝。 出現(xiàn)這樣的情況時(shí)候谣辞,被喚醒的goroutine需要排隊(duì)在隊(duì)列的前面。 如果被喚醒的goroutine有超過1ms沒有獲取到mutex鎖沐扳,那么它就會(huì)變?yōu)?饑餓模式泥从。 在饑餓模式中,mutex鎖直接從解鎖的goroutine交給隊(duì)列前面的goroutine沪摄。新達(dá)到的goroutine也不會(huì)去爭(zhēng)奪mutex鎖(即使沒有鎖歉闰,也不能去自旋),而是到等待隊(duì)列尾部排隊(duì)卓起。

饑餓模式

有一個(gè)goroutine獲取到mutex鎖了和敬,如果它滿足下條件中的任意一個(gè),mutex將會(huì)切換回去正常模式: 1. 是等待隊(duì)列中的最后一個(gè)goroutine 戏阅。2. 它的等待時(shí)間不超過1ms昼弟。 正常模式有更好的性能,因?yàn)間oroutine可以連續(xù)多次獲得mutex鎖奕筐; 饑餓模式需要預(yù)防隊(duì)列尾部goroutine一直無法獲取mutex鎖的問題舱痘。

嘗試獲取mutex的goroutine也有狀態(tài),有可能它是新來的goroutine离赫,也有可能是被喚醒的goroutine, 可能是處于正常狀態(tài)的goroutine, 也有可能是處于饑餓狀態(tài)的goroutine

四個(gè)重要的方法

runtime_canSpin: 在 src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_canSpin芭逝; 表示是否可以保守的自旋,golang中自旋鎖并不會(huì)一直自旋下去渊胸,在runtime包中runtime_canSpin方法做了一些限制, 傳遞過來的iter大等于4或者cpu核數(shù)小等于1旬盯,最大邏輯處理器大于1,至少有個(gè)本地的P隊(duì)列,并且本地的P隊(duì)列可運(yùn)行G隊(duì)列為空胖翰。
runtime_doSpin: 在 src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_doSpin接剩;表示 會(huì)調(diào)用procyield函數(shù), 該函數(shù)也是匯編語言實(shí)現(xiàn)萨咳。函數(shù)內(nèi)部循環(huán)調(diào)用PAUSE指令懊缺。PAUSE指令什么都不做, 但是會(huì)消耗CPU時(shí)間培他,在執(zhí)行PAUSE指令時(shí)鹃两,CPU不會(huì)對(duì)它做不必要的優(yōu)化。
runtime_SemacquireMutex:在 src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_SemacquireMutex舀凛;表示通過信號(hào)量 阻塞當(dāng)前協(xié)程 俊扳。
runtime_Semrelease: 在src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_Semrelease。表示通過信號(hào)量解除當(dāng)前協(xié)程阻塞腾降。

func throw(string) // 被定義在 runtime 包中拣度,src/runtime/panic.go 的 sync_throw 方法
// mutex 是一個(gè)互斥鎖
// 零值是沒有被上鎖的互斥鎖。
//
// 首次使用后螃壤,不得復(fù)制互斥鎖抗果。(意思是不能復(fù)制值,可以做成引用復(fù)制)
type Mutex struct {
    // 將一個(gè)32位整數(shù)拆分為
    // 當(dāng)前阻塞的goroutine數(shù)目(29位)|饑餓狀態(tài)(1位)|喚醒狀態(tài)(1位)|鎖狀態(tài)(1位) 的形式奸晴,來簡(jiǎn)化字段設(shè)計(jì)
    state int32
 
    // 信號(hào)量
    sema uint32
}
 
// 鎖接口
type Locker interface {
    Lock()
    Unlock()
}
 
const (
    // 定義鎖的狀態(tài)
    mutexLocked      = 1 << iota // 1 表示是否被鎖定  0001 含義:用最后一位表示當(dāng)前對(duì)象鎖的狀態(tài)冤馏,0-未鎖住 1-已鎖住
    mutexWoken                   // 2 表示是否被喚醒  0010 含義:用倒數(shù)第二位表示當(dāng)前對(duì)象是否被喚醒 0- 未喚醒 1-喚醒  【注意: 未被喚醒并不是指 休眠,而是指為了讓所能被設(shè)置 被喚醒的一個(gè)初始值】
    mutexStarving                // 4 表示是否饑餓   0100 含義:用倒數(shù)第三位表示當(dāng)前對(duì)象是否為饑餓模式寄啼,0為正常模式逮光,1為饑餓模式。
    mutexWaiterShift = iota      // 3 表示 從倒數(shù)第四位往前的bit位表示在排隊(duì)等待的goroutine數(shù)目(共對(duì)于 32位中占用 29 位)
 
    //
    /** 互斥量可分為兩種操作模式:正常和饑餓墩划。
    【正常模式】涕刚,等待的goroutines按照FIFO(先進(jìn)先出)順序排隊(duì),但是goroutine被喚醒之后并不能立即得到mutex鎖乙帮,它需要與新到達(dá)的goroutine爭(zhēng)奪mutex鎖杜漠。
    因?yàn)樾碌竭_(dá)的goroutine已經(jīng)在CPU上運(yùn)行了,所以被喚醒的goroutine很大概率是爭(zhēng)奪mutex鎖是失敗的察净。出現(xiàn)這樣的情況時(shí)候驾茴,被喚醒的goroutine需要排隊(duì)在隊(duì)列的前面。
    如果被喚醒的goroutine有超過1ms沒有獲取到mutex鎖氢卡,那么它就會(huì)變?yōu)轲囸I模式锈至。
    在饑餓模式中,mutex鎖直接從解鎖的goroutine交給隊(duì)列前面的goroutine译秦。新達(dá)到的goroutine也不會(huì)去爭(zhēng)奪mutex鎖(即使沒有鎖峡捡,也不能去自旋)击碗,而是到等待隊(duì)列尾部排隊(duì)。
    【饑餓模式】棋返,鎖的所有權(quán)將從unlock的gorutine直接交給交給等待隊(duì)列中的第一個(gè)延都。新來的goroutine將不會(huì)嘗試去獲得鎖雷猪,即使鎖看起來是unlock狀態(tài), 也不會(huì)去嘗試自旋操作睛竣,而是放在等待隊(duì)列的尾部。如果有一個(gè)等待的goroutine獲取到mutex鎖了求摇,如果它滿足下條件中的任意一個(gè)射沟,mutex將會(huì)切換回去正常模式:
    1. 是等待隊(duì)列中的最后一個(gè)goroutine
    2. 它的等待時(shí)間不超過1ms。
    正常模式:有更好的性能与境,因?yàn)間oroutine可以連續(xù)多次獲得mutex鎖验夯;
    饑餓模式:能阻止尾部延遲的現(xiàn)象,對(duì)于預(yù)防隊(duì)列尾部goroutine一致無法獲取mutex鎖的問題摔刁。
    */
    starvationThresholdNs = 1e6 // 1ms
)
 
// 如果鎖已經(jīng)在使用中挥转,則調(diào)用goroutine 直到互斥鎖可用為止。
/**
在此之前我們必須先說下 四個(gè)重要的方法共屈;
【runtime_canSpin】绑谣,【runtime_doSpin】,【runtime_SemacquireMutex】拗引,【runtime_Semrelease】
【runtime_canSpin】: 在 src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_canSpin借宵; 表示 比較保守的自旋,
                    golang中自旋鎖并不會(huì)一直自旋下去矾削,在runtime包中runtime_canSpin方法做了一些限制,
                    傳遞過來的iter大等于4或者cpu核數(shù)小等于1壤玫,最大邏輯處理器大于1,至少有個(gè)本地的P隊(duì)列哼凯,
                    并且本地的P隊(duì)列可運(yùn)行G隊(duì)列為空欲间。
【runtime_doSpin】: 在src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_doSpin;表示 會(huì)調(diào)用procyield函數(shù)断部,
                    該函數(shù)也是匯編語言實(shí)現(xiàn)猎贴。函數(shù)內(nèi)部循環(huán)調(diào)用PAUSE指令。PAUSE指令什么都不做家坎,
                    但是會(huì)消耗CPU時(shí)間嘱能,在執(zhí)行PAUSE指令時(shí),CPU不會(huì)對(duì)它做不必要的優(yōu)化虱疏。
【runtime_SemacquireMutex】:在 src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_SemacquireMutex惹骂;表示通過信號(hào)量 阻塞當(dāng)前協(xié)程
【runtime_Semrelease】: 在src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_Semrelease
*/
func (m *Mutex) Lock() {
    // 如果m.state為 0,說明當(dāng)前的對(duì)象還沒有被鎖住做瞪,進(jìn)行原子性賦值操作設(shè)置為mutexLocked狀態(tài)对粪,CompareAnSwapInt32返回true
    // 否則說明對(duì)象已被其他goroutine鎖住右冻,不會(huì)進(jìn)行原子賦值操作設(shè)置,CopareAndSwapInt32返回false
    /**
    如果mutext的state沒有被鎖著拭,也沒有等待/喚醒的goroutine, 鎖處于正常狀態(tài)纱扭,那么獲得鎖,返回.
    比如鎖第一次被goroutine請(qǐng)求時(shí)儡遮,就是這種狀態(tài)乳蛾。或者鎖處于空閑的時(shí)候鄙币,也是這種狀態(tài)
     */
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        if race.Enabled {
            race.Acquire(unsafe.Pointer(m))
        }
        return
    }
    /** 在 鎖定沒有成功的時(shí)候肃叶,才會(huì)往下面走 */
 
    // 首先判斷是否已經(jīng)加鎖并處于 正常模式,
    // 將原先鎖的state & (1 和 4 | 的結(jié)果十嘿,目的就是為了檢驗(yàn) state 是處于 1 還是 4 狀態(tài)因惭, 還是兩者都是.
    // 如果與1相等,則說明此時(shí)處于 正常模式并且已經(jīng)加鎖绩衷,而后判斷當(dāng)前協(xié)程是否可以自旋蹦魔。
    // 如果可以自旋,則通過右移三位判斷是否還有協(xié)程正在等待這個(gè)鎖咳燕,
    // 如果有勿决,并通過 低2位 判斷是否該所處于被喚醒狀態(tài),
    // 如果并沒有迟郎,則將其狀態(tài)量設(shè)為被喚醒的狀態(tài)剥险,之后進(jìn)行自旋,直到該協(xié)程自旋數(shù)量達(dá)到上限宪肖,
    // 或者當(dāng)前鎖被解鎖表制,
    // 或者當(dāng)前鎖已經(jīng)處于 饑餓模式
 
 
    // 標(biāo)記本goroutine的等待時(shí)間
    // 開始等待時(shí)間戳
    var waitStartTime int64
 
    // 本goroutine是否已經(jīng)處于饑餓狀態(tài)
    // 饑餓模式標(biāo)識(shí) true: 饑餓  false: 未饑餓
    starving := false
 
    // 本goroutine是否已喚醒
    // 被喚醒標(biāo)識(shí)  true: 被喚醒   flase: 未被喚醒
    awoke := false
 
    // 自旋次數(shù)
    iter := 0
 
    // 保存當(dāng)前對(duì)象鎖狀態(tài),做對(duì)比用
    old := m.state
 
    // for 來實(shí)現(xiàn) CAS(Compare and Swap) 非阻塞同步算法 (對(duì)比交換)
    for {
        // 不要在饑餓模式下自旋控乾,將鎖的控制權(quán)交給阻塞任務(wù)么介,否則無論如何 當(dāng)前goroutine都無法獲得互斥鎖。
 
        // 相當(dāng)于xxxx...x0xx & 0101 = 01蜕衡,當(dāng)前對(duì)象鎖被使用
        // old & (是否鎖定|是否饑餓) == 是否鎖定
        // runtime_canSpin() 表示 是否可以自旋壤短。runtime_canSpin返回true,可以自旋慨仿。即: 判斷當(dāng)前goroutine是否可以進(jìn)入自旋鎖
        /**
        第一個(gè)條件:是state已被鎖久脯,但是不是饑餓狀態(tài)。如果時(shí)饑餓狀態(tài)镰吆,自旋時(shí)沒有用的帘撰,鎖的擁有權(quán)直接交給了等待隊(duì)列的第一個(gè)。
        第二個(gè)條件:是還可以自旋万皿,多核摧找、壓力不大并且在一定次數(shù)內(nèi)可以自旋核行, 具體的條件可以參考`sync_runtime_canSpin`的實(shí)現(xiàn)的止。
        如果滿足這兩個(gè)條件线欲,不斷自旋來等待鎖被釋放、或者進(jìn)入饑餓狀態(tài)不脯、或者不能再自旋综苔。
         */
        if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
 
 
            // 主動(dòng)旋轉(zhuǎn)是有意義的惩系。試著設(shè)置 mutexWoken (鎖喚醒)標(biāo)志,告知解鎖休里,不喚醒其他阻塞的goroutines蛆挫。
            // old&mutexWoken == 0 再次確定是否被喚醒: xxxx...xx0x & 0010 = 0
            // old>>mutexWaiterShift != 0 查看是否有g(shù)oroution在排隊(duì)
            // tomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) 將對(duì)象鎖在老狀態(tài)上追加喚醒狀態(tài):xxxx...xx0x | 0010 = xxxx...xx1x
 
            // 如果當(dāng)前標(biāo)識(shí)位 awoke為 未被喚醒 && (old 也為 未被喚醒) && 有正在等待的 goroutine && 則修改 old 為 被喚醒
            // 且修改標(biāo)識(shí)位 awoke 為 true 被喚醒
            /**
            自旋的過程中如果發(fā)現(xiàn)state還沒有設(shè)置woken標(biāo)識(shí)赃承,則設(shè)置它的woken標(biāo)識(shí)妙黍, 并標(biāo)記自己為被喚醒。
             */
            if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
 
                // 更改標(biāo)識(shí)位為 喚醒true
                awoke = true
            }
 
            // 否則: 進(jìn)入自旋
            // 進(jìn)入自旋鎖后當(dāng)前goroutine并不掛起瞧剖,仍然在占用cpu資源拭嫁,所以重試一定次數(shù)后,不會(huì)再進(jìn)入自旋鎖邏輯
            runtime_doSpin()
 
            // 累加自旋次數(shù)
            iter++
 
            // 更新中轉(zhuǎn)變量
            // 保存mutex對(duì)象即將被設(shè)置成的狀態(tài)
            old = m.state
            continue
        }
 
 
        // 以下代碼是不使用**自旋**的情況
 
        /**
         到了這一步抓于, state的狀態(tài)可能是:
         1. 鎖還沒有被釋放做粤,鎖處于正常狀態(tài)
         2. 鎖還沒有被釋放, 鎖處于饑餓狀態(tài)
         3. 鎖已經(jīng)被釋放捉撮, 鎖處于正常狀態(tài)
         4. 鎖已經(jīng)被釋放怕品, 鎖處于饑餓狀態(tài)
         并且本gorutine的 awoke可能是true, 也可能是false (其它goutine已經(jīng)設(shè)置了state的woken標(biāo)識(shí))
         new 復(fù)制 state的當(dāng)前狀態(tài), 用來設(shè)置新的狀態(tài)
         old 是鎖當(dāng)前的狀態(tài)
         */
 
        new := old
 
 
        /** 下面的幾個(gè) if 分別是并列語句巾遭,來判斷如給 設(shè)置 state 的new 狀態(tài) */
 
        /**
        如果old state狀態(tài)不是饑餓狀態(tài), new state 設(shè)置鎖肉康, 嘗試通過CAS獲取鎖,
        如果old state狀態(tài)是饑餓狀態(tài), 則不設(shè)置new state的鎖,因?yàn)轲囸I狀態(tài)下鎖直接轉(zhuǎn)給等待隊(duì)列的第一個(gè).
         */
        // 不要試圖獲得饑餓goroutine的互斥鎖灼舍,新來的goroutines必須排隊(duì)吼和。
        // 對(duì)象鎖饑餓位被改變 為 1 ,說明處于饑餓模式
        // xxxx...x0xx & 0100 = 0xxxx...x0xx
 
 
 
        /**【一】如果是正常狀態(tài) (如果是正常骑素,則可以競(jìng)爭(zhēng)到鎖) */
        if old&mutexStarving == 0 {
 
            // xxxx...x0xx | 0001 = xxxx...x0x1炫乓,將標(biāo)識(shí)對(duì)象鎖被鎖住
            new |= mutexLocked
        }
 
        /** 【二】處于饑餓且鎖被占用 狀態(tài)下  */
        // xxxx...x1x1 & (0001 | 0100) => xxxx...x1x1 & 0101 != 0;當(dāng)前mutex處于饑餓模式并且鎖已被占用,新加入進(jìn)來的goroutine放到隊(duì)列后面献丑,所以 等待者數(shù)目 +1
        if old&(mutexLocked|mutexStarving) != 0 {
            // 更新阻塞goroutine的數(shù)量,表示mutex的等待goroutine數(shù)目加1
            // 首先末捣,如果此時(shí)還是由于別的協(xié)程的占用 無法獲得鎖 或者 處于 饑餓模式,都在其state加8表示有新的協(xié)程正在處于等待狀態(tài)
            new += 1 << mutexWaiterShift
        }
 
        /**
        如果之前由于自旋而將該鎖喚醒创橄,那么此時(shí)將其低二位的狀態(tài)量重置為0 (即 未被喚醒)箩做。
        之后判斷starving是否為true,如果為true說明在上一次的循環(huán)中筐摘,
        鎖需要被定義為 饑餓模式卒茬,那么在這里就將相應(yīng)的狀態(tài)量低3位設(shè)置為1表示進(jìn)入饑餓模式
        */
        /***
        【三】
        如果當(dāng)前goroutine已經(jīng)處于饑餓狀態(tài) (表示當(dāng)前 goroutine 的饑餓標(biāo)識(shí)位 starving)船老, 并且old state的已被加鎖,
        將new state的狀態(tài)標(biāo)記為饑餓狀態(tài), 將鎖轉(zhuǎn)變?yōu)轲囸I狀態(tài).
         */
        // 當(dāng)前的goroutine將互斥鎖轉(zhuǎn)換為饑餓模式。但是圃酵,如果互斥鎖當(dāng)前沒有解鎖柳畔,就不要打開開關(guān),設(shè)置mutex狀態(tài)為饑餓模式。Unlock預(yù)期有饑餓的goroutine
        // old&mutexLocked != 0  xxxx...xxx1 & 0001 != 0郭赐;鎖已經(jīng)被占用
        // 如果 饑餓且已被鎖定
        if starving && old&mutexLocked != 0 {
 
            // 【追加】饑餓狀態(tài)
            new |= mutexStarving
        }
 
 
        /**
        【四】
        如果本goroutine已經(jīng)設(shè)置為喚醒狀態(tài), 需要清除new state的喚醒標(biāo)記, 因?yàn)楸緂oroutine要么獲得了鎖薪韩,要么進(jìn)入休眠,
        總之state的新狀態(tài)不再是woken狀態(tài).
         */
        // 如果 goroutine已經(jīng)被喚醒捌锭,因此需要在兩種情況下重設(shè)標(biāo)志
        if awoke {
            // xxxx...xx0x & 0010 == 0,如果喚醒標(biāo)志為與awoke的值不相協(xié)調(diào)就panic
            // 即 state 為 未被喚醒
            if new&mutexWoken == 0 {
                panic("sync: inconsistent mutex state")
            }
 
            // new & (^mutexWoken) => xxxx...xxxx & (^0010) => xxxx...xxxx & 1101 = xxxx...xx0x
            // 設(shè)置喚醒狀態(tài)位0,被  未喚醒【只是為了俘陷, 下次被可被設(shè)置為i被喚醒的 初識(shí)化標(biāo)識(shí),而不是指休眠】
            new &^= mutexWoken
        }
 
        /**
        之后嘗試通過cas將 new 的state狀態(tài)量賦值給state观谦,
        如果失敗拉盾,則重新獲得其 state在下一步循環(huán)重新重復(fù)上述的操作。
        如果成功豁状,首先判斷已經(jīng)阻塞時(shí)間 (通過 標(biāo)記本goroutine的等待時(shí)間 waitStartTime )捉偏,如果為零,則從現(xiàn)在開始記錄
        */
 
        // 將新的狀態(tài)賦值給 state
        // 注意new的鎖標(biāo)記不一定是true, 也可能只是標(biāo)記一下鎖的state是饑餓狀態(tài)
        if atomic.CompareAndSwapInt32(&m.state, old, new) {
            /**
             如果old state的狀態(tài)是未被鎖狀態(tài)泻红,并且鎖不處于饑餓狀態(tài),
             那么當(dāng)前goroutine已經(jīng)獲取了鎖的擁有權(quán)夭禽,返回
             */
            // xxxx...x0x0 & 0101 = 0,表示可以獲取對(duì)象鎖 (即 還是判斷之前的狀態(tài)谊路,鎖不是饑餓 也不是被被鎖定讹躯, 所已經(jīng)可用了)
            if old&(mutexLocked|mutexStarving) == 0 {
                break // 結(jié)束cas
            }
            // 以下的操作都是為了判斷是否從【饑餓模式】中恢復(fù)為【正常模式】
            // 判斷處于FIFO還是LIFO模式
            // 如果等待時(shí)間不為0 那么就是 LIFO
            // 在正常模式下,等待的goroutines按照FIFO(先進(jìn)先出)順序排隊(duì)
 
            /**
            設(shè)置/計(jì)算本goroutine的等待時(shí)間
             */
            queueLifo := waitStartTime != 0
            if waitStartTime == 0 {
                // 更新等待時(shí)間
                waitStartTime = runtime_nanotime()
            }
 
 
            // 通過runtime_SemacquireMutex()通過信號(hào)量將當(dāng)前協(xié)程阻塞
            // 函數(shù) runtime_SemacquireMutex 定義在 sema.go
            /**
            既然未能獲取到鎖缠劝, 那么就使用 [sleep原語] 阻塞本goroutine
            如果是新來的goroutine,queueLifo=false, 加入到等待隊(duì)列的尾部潮梯,耐心等待
            如果是喚醒的goroutine, queueLifo=true, 加入到等待隊(duì)列的頭部
             */
            runtime_SemacquireMutex(&m.sema, queueLifo)
 
            // 當(dāng)之前調(diào)用 runtime_SemacquireMutex 方法將當(dāng)前新進(jìn)來爭(zhēng)奪鎖的協(xié)程掛起后,如果協(xié)程被喚醒剩彬,那么就會(huì)繼續(xù)下面的流程
            // 如果當(dāng)前 饑餓狀態(tài)標(biāo)識(shí)為 饑餓 || 當(dāng)前時(shí)間 - 開始等待時(shí)間 > 1 ms 則 都切換為饑餓狀態(tài)標(biāo)識(shí)
            /**
            使用 [sleep原語] 之后酷麦,此goroutine被喚醒
            計(jì)算當(dāng)前goroutine是否已經(jīng)處于饑餓狀態(tài).
             */
            starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
            // 刷新下 中轉(zhuǎn)變量
            /** 得到當(dāng)前的鎖狀態(tài) */
            old = m.state
 
 
            /**
            如果當(dāng)前的state已經(jīng)是饑餓狀態(tài)
            那么鎖應(yīng)該處于Unlock狀態(tài),那么應(yīng)該是鎖被直接交給了本goroutine
             */
            // xxxx...x1xx & 0100 != 0  處于 饑餓狀態(tài)
            if old&mutexStarving != 0 {
 
 
                 /**
                 如果當(dāng)前的state已被鎖喉恋,或者已標(biāo)記為喚醒沃饶, 或者等待的隊(duì)列中為空,
                 那么state是一個(gè)非法狀態(tài)
                  */
                // xxxx...xx11 & 0011 != 0 又可能是被鎖定,又可能是被喚醒 或者 沒有等待的goroutine
                if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
                    panic("sync: inconsistent mutex state")
                }
 
                // delta 表示當(dāng)前狀態(tài)下的等待數(shù)
                // 否則下一次的循環(huán)中將該鎖這是為 饑餓模式轻黑。
                // 如果已經(jīng)是這個(gè)模式糊肤,那么就會(huì)將 狀態(tài)量的等待數(shù) 減1
                /**
                當(dāng)前goroutine用來設(shè)置鎖,并將等待的goroutine數(shù)減1.
                lock狀態(tài) -一個(gè)gorotine數(shù)氓鄙,表示 狀態(tài) delta == (lock + (減去一個(gè)等待goroutine數(shù)))
                 */
                delta := int32(mutexLocked - 1<<mutexWaiterShift)
                // 并判斷當(dāng)前如果已經(jīng)沒有等待的協(xié)程馆揉,就沒有必要繼續(xù)維持 饑餓模式,同時(shí)也沒必要繼續(xù)執(zhí)行該循環(huán)(當(dāng)前只有一個(gè)協(xié)程在占用鎖)
                /**
                如果本goroutine并不處于饑餓狀態(tài)抖拦,或者它是最后一個(gè)等待者升酣,
                那么我們需要把鎖的state狀態(tài)設(shè)置為正常模式.
                 */
                if !starving || old>>mutexWaiterShift == 1 {
 
                    // 退出饑餓模式舷暮。
                    // 在這里做到并考慮等待時(shí)間至關(guān)重要。
                    // 饑餓模式是如此低效噩茄,一旦將互斥鎖切換到饑餓模式下面,兩個(gè)goroutine就可以無限鎖定。
                    delta -= mutexStarving
                }
                // 設(shè)置新state, 因?yàn)橐呀?jīng)獲得了鎖绩聘,退出沥割、返回
                atomic.AddInt32(&m.state, delta)
                break
            }
            // 修改為 本goroutine 是否被喚醒標(biāo)識(shí)位
            /**
            如果當(dāng)前的鎖是正常模式,本goroutine被喚醒凿菩,自旋次數(shù)清零机杜,從for循環(huán)開始處重新開始
             */
            awoke = true
            // 自旋計(jì)數(shù) 初始化
            iter = 0
        } else {
            // 如果CAS不成功,重新獲取鎖的state, 從for循環(huán)開始處重新開始 繼續(xù)上述動(dòng)作
            old = m.state
        }
    }
 
    if race.Enabled {
        race.Acquire(unsafe.Pointer(m))
    }
}
 
// 解鎖一個(gè)未被鎖定的互斥鎖時(shí)衅谷,是會(huì)報(bào)錯(cuò)
// 鎖定的互斥鎖與特定的goroutine無關(guān)椒拗。
// 允許一個(gè)goroutine鎖定Mutex然后
// 安排另一個(gè)goroutine解鎖它。
func (m *Mutex) Unlock() {
    if race.Enabled {
        _ = m.state
        race.Release(unsafe.Pointer(m))
    }
    /** 如果state不是處于鎖的狀態(tài), 那么就是Unlock根本沒有加鎖的mutex, panic  */
    // state -1 標(biāo)識(shí)解鎖 (移除鎖定標(biāo)記)
    new := atomic.AddInt32(&m.state, -mutexLocked)
 
    /**
    釋放了鎖会喝,還得需要通知其它等待者
    被通知的 goroutine 會(huì)去做下面的事情
    鎖如果處于饑餓狀態(tài)陡叠,直接交給等待隊(duì)列的第一個(gè), 喚醒它,讓它去獲取鎖
    鎖如果處于正常狀態(tài)肢执,則需要喚醒對(duì)頭的 goroutine 讓它和新來的goroutine去競(jìng)爭(zhēng)鎖,當(dāng)然極大幾率為失敗译红,
        這時(shí)候 被喚醒的goroutine需要排隊(duì)在隊(duì)列的前面 (然后自旋)预茄。如果被喚醒的goroutine有超過1ms沒有獲取到mutex鎖,那么它就會(huì)變?yōu)轲囸I模式
     */
 
 
    // 再次校驗(yàn)下 標(biāo)識(shí)侦厚,new state如果是正常狀態(tài), 驗(yàn)證鎖狀態(tài)是否符合
    if (new+mutexLocked)&mutexLocked == 0 {
        panic("sync: unlock of unlocked mutex")
    }
    // xxxx...x0xx & 0100 = 0 ;判斷是否處于正常模式
    if new&mutexStarving == 0 {
 
        // 記錄緩存值
        old := new
        for {
 
            // 如果沒有等待的goroutine或goroutine不處于空閑耻陕,則無需喚醒任何人
            // 在饑餓模式下,鎖的所有權(quán)直接從解鎖goroutine交給下一個(gè) 正在等待的goroutine (等待隊(duì)列中的第一個(gè))刨沦。
            // 注意: old&(mutexLocked|mutexWoken|mutexStarving) 中诗宣,因?yàn)樵谧钌厦嬉呀?jīng) -mutexLocked 并且進(jìn)入了 if new&mutexStarving == 0
            // 說明目前 只有在還有g(shù)oroutine 或者 被喚醒的情況下才會(huì) old&(mutexLocked|mutexWoken|mutexStarving) != 0
            // 即:當(dāng)休眠隊(duì)列內(nèi)的等待計(jì)數(shù)為 0  或者 是正常但是 處于被喚醒或者被鎖定狀態(tài),退出
            // old&(mutexLocked|mutexWoken|mutexStarving) != 0     xxxx...x0xx & (0001 | 0010 | 0100) => xxxx...x0xx & 0111 != 0
            /**
             如果沒有等待的goroutine, 或者鎖不處于空閑的狀態(tài)想诅,直接返回.
             */
            if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
                return
            }
 
            // 減少等待goroutine個(gè)數(shù)召庞,并添加 喚醒標(biāo)識(shí)
            new = (old - 1<<mutexWaiterShift) | mutexWoken
 
            /** 設(shè)置新的state, 這里通過 信號(hào)量 去喚醒一個(gè)阻塞的goroutine去獲取鎖. */
            if atomic.CompareAndSwapInt32(&m.state, old, new) {
 
                // 釋放鎖,發(fā)送釋放信號(hào) (解除 阻塞信號(hào)量)
                runtime_Semrelease(&m.sema, false)
                return
            }
            // 賦值給中轉(zhuǎn)變量,然后啟動(dòng)下一輪
            old = m.state
        }
    } else {
 
        /**
        饑餓模式下:
        直接將鎖的擁有權(quán)傳給等待隊(duì)列中的第一個(gè).
        注意:
        此時(shí)state的mutexLocked還沒有加鎖来破,喚醒的goroutine會(huì)設(shè)置它篮灼。
        在此期間,如果有新的goroutine來請(qǐng)求鎖徘禁, 因?yàn)閙utex處于饑餓狀態(tài)诅诱, mutex還是被認(rèn)為處于鎖狀態(tài),
        新來的goroutine不會(huì)把鎖搶過去.
         */
        runtime_Semrelease(&m.sema, true)
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末送朱,一起剝皮案震驚了整個(gè)濱河市娘荡,隨后出現(xiàn)的幾起案子干旁,更是在濱河造成了極大的恐慌,老刑警劉巖炮沐,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疤孕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡央拖,警方通過查閱死者的電腦和手機(jī)祭阀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲜戒,“玉大人专控,你說我怎么就攤上這事《舨停” “怎么了伦腐?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)失都。 經(jīng)常有香客問我柏蘑,道長(zhǎng),這世上最難降的妖魔是什么粹庞? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任咳焚,我火速辦了婚禮,結(jié)果婚禮上庞溜,老公的妹妹穿的比我還像新娘革半。我一直安慰自己,他們只是感情好流码,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布又官。 她就那樣靜靜地躺著,像睡著了一般漫试。 火紅的嫁衣襯著肌膚如雪六敬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天驾荣,我揣著相機(jī)與錄音外构,去河邊找鬼。 笑死秘车,一個(gè)胖子當(dāng)著我的面吹牛典勇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叮趴,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼割笙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伤溉,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤般码,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乱顾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體板祝,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年走净,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了券时。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伏伯,死狀恐怖橘洞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情说搅,我是刑警寧澤炸枣,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站弄唧,受9級(jí)特大地震影響适肠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜候引,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一侯养、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧背伴,春花似錦沸毁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽携兵。三九已至疾掰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間徐紧,已是汗流浹背静檬。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留并级,地道東北人拂檩。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嘲碧,于是被迫代替她去往敵國和親稻励。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容