Go的sync/mutex實現

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

概述

sync/mutex是Go語言底層基礎對象之一,用於構建多個goroutine間的同步邏輯,因此被大量高層對象所使用。
其工作模型類似於Linux核心的futex對象,具體實現極為簡潔,效能也有保證。

資料結構

type Mutex struct {    state int32        sema  uint32   }                  

mutex對象僅有兩個數值欄位,分為為state(儲存狀態)和sema(用於計算休眠goroutine數量的訊號量)。
初始化時填入的0值將mutex設定在未鎖定狀態,同時保證時間開銷最小。
這一特性允許將mutex作為其它對象的子物件使用。

state欄位

state被定義為int32類型,允許為其調用原子方法(sync/atomic),從而原子化地設定狀態。
每個state欄位均劃分三個狀態段,含義如下:

    31          3 2 1 0    +----~~~----+-+-+-+    |     S     | |W|L|    +----~~~----+-+-+-+     |           | | |     |           | | \--- 鎖定狀態,0表示未鎖定,1表示鎖定     |           | |     |           | \----- 喚醒事件,0表示無事件,1表示mutex已被解除鎖定,可以喚醒等待其它goroutine     |           |       |           \------- 保留位,保持為0     |     \------------------- 等待喚醒以嘗試鎖定的goroutine的計數,0表示沒有等待者

方法

Lock() / 鎖定

func (m *Mutex) Lock() {    // 快速路徑:直接鎖定mutex    // 記為FG同步點:Fast Grab    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {        // 僅在沒有等待者、沒有喚醒事件、沒有鎖定的情況下執行        if raceenabled {            raceAcquire(unsafe.Pointer(m))        }        return    }    awoke := false    for {        old := m.state        new := old | mutexLocked        if old&mutexLocked != 0 {            // 處於鎖定狀態,增加等待者計數            new = old + 1<<mutexWaiterShift        }        if awoke {            // goroutine已被喚醒,“消費”喚醒事件,重設標誌位            new &^= mutexWoken        }        // 記為G同步點:Grab        if atomic.CompareAndSwapInt32(&m.state, old, new) {            // 新舊狀態一致,沒有被其它goroutine修改            if old&mutexLocked == 0 {                // 成功鎖定                break            }            // 休眠等待            runtime_Semacquire(&m.sema)            awoke = true        }        // 新舊狀態不一致,重新取狀態並嘗試鎖定    }    if raceenabled {        raceAcquire(unsafe.Pointer(m))    }}

Unlock() / 解除鎖定

func (m *Mutex) Unlock() {    if raceenabled {        _ = m.state        raceRelease(unsafe.Pointer(m))    }    // 快速路徑:直接解除鎖定    // 記為FD同步點:Fast Drop    new := atomic.AddInt32(&m.state, -mutexLocked)    if (new+mutexLocked)&mutexLocked == 0 {        // 連續解除兩次以上        panic("sync: unlock of unlocked mutex")    }    old := new    for {        // 如果沒有等待者,或已經有等待者被喚醒,或已經有goroutine鎖定mutex,        // 則無需嘗試喚醒        if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 {            return        }        // 產生喚醒事件,嘗試喚醒某個等待者        new = (old - 1<<mutexWaiterShift) | mutexWoken        // 記為W同步點:Wake Up        if atomic.CompareAndSwapInt32(&m.state, old, new) {            // 新舊狀態一致,沒有被其它goroutine修改            // 嘗試喚醒            runtime_Semrelease(&m.sema)            return        }        // 新舊狀態不一致,重新取狀態並嘗試喚醒        old = m.state    }}

可以觀察到,兩個函數一共有四個原子函數調用點,也就是狀態同步點。
一旦原子函數調用失敗,說明狀態已被其它goroutine改變,需要重新擷取狀態以決定後續動作。

調用者

mutex不與具體goroutine掛鈎,可被任意的goroutine調用,但需要注意一下幾點:

  1. 同一個goroutine不能在“連續”調用Lock()函數兩次,否則會導致死結;
  2. 在確保調用順序正確的前提下,完全可以在一個goroutine中調用Lock(),在另一個中調用Unlock();
  3. 同一個mutex不能被“連續”調用Unlock()函數兩次,否則會導致異常。

狀態遷移

狀態列表

state的三個狀態段一共有如下狀態組合(-表示0):

狀態組合 休眠者 喚醒事件 鎖定
--- - - -
--L - -
-WL -
S-L -
SW- -
S-- - -
-W- - -
SWL

結合約步點分析,可以得到如下規則:
1. 非喚醒goroutine在嘗試鎖定時不消耗喚醒事件,對該狀態段不做改變;
2. 已喚醒goroutine一定會消耗喚醒事件,將重設該狀態段;
3. 解除鎖定後才嘗試發送喚醒事件。

遷移路徑

只有一個goroutine調用的情況

這種情況下,狀態只在-----L之間遷移(路徑1和2),且只涉及到FG和D兩個同步點。

有兩個goroutine調用的情況

這種情況下,狀態涉及-----LS-LS---W--WL六個狀態。
假設兩個goroutine分別稱為X和Y:

1. 當X已經鎖定mutex,而Y嘗試鎖定時,會從`--L`遷移到`S-L`(路徑3);  2. 當X解除鎖定後,會從`S-L`遷移到`S--`(路徑5),此時Y還在休眠中;  3. 當X發送喚醒事件後,會從`S--`狀態遷移到`-W-`(路徑7),並嘗試喚醒休眠者(即Y),此時有三條路徑:      3.1 Y成功鎖定mutex,則從`-W-`遷移到`--L`(路徑8),X若嘗試鎖定,則進一步遷移到`S-L`(路徑3);    3.2 X又嘗試鎖定mutex,則從`-W-`遷移到`-WL`(路徑14),Y因被喚醒而嘗試鎖定,則進一步遷移到`S-L`(路徑17);      3.3 X又嘗試鎖定mutex,且在Y嘗試鎖定前解除鎖定,則從`-W-`遷移到`-WL`(路徑14)而後又遷移回`-W-`(路徑15)。   

有多個goroutine調用的情況

這種情況是兩個goroutine調用情況的延續,在原基礎上再涉及-WLSWL兩個狀態。
假設三個goroutine分別稱為X、Y和Z:

1. 當X已經鎖定mutex,則Y在等待,且Z嘗試鎖定,會從`S-L`遷移到其自身(路徑4);  2. 當X解除鎖定、還未喚醒Y時,此時若Z嘗試鎖定,會從`S--`遷移回`S-L`(路徑6);  3. 當X解除鎖定、且喚醒Y後(路徑9),此時若Z嘗試鎖定,會從`SW-`遷移到`SWL`(路徑11),此時有三條路徑:      3.1 Y發現mutex已被鎖定,而進一步遷移到`S-L`狀態(路徑18);      3.2 Z迅速解除鎖定,狀態遷移回`SW-`(路徑12),Y嘗試鎖定,進一步遷移到`S-L`(路徑10);      3.3 更多的goroutine嘗試鎖定,會一直遷移回`SWL`(路徑13)。4. 當X喚醒Y、且Z嘗試鎖定成功,則從`-WL`遷移到`SWL`(路徑16)。

附錄

附狀態遷移圖的Graphviz源碼。

graph {    node [shape="circle"];    edge [dir="forward", len="2.0"];    rankdir=LR;    nnn [label="---"];    nnl [label="--L"];    swn [label="SW-"];    snn [label="S--", pos="0,0"];    snl [label="S-L"];    nwn [label="-W-"];    nwl [label="-WL"];    swl [label="SWL"];    nnn -- nnl [label="1"];    nnl -- nnn [label="2"];    nnl -- snl [label="3"];    snl -- snl [label="4"];    snl -- snn [label="5"];    snn -- snl [label="6"];    snn -- nwn [label="7"];    nwn -- nnl [label="8"];    snn -- swn [label="9"];    swn -- snl [label="10"];    swn -- swl [label="11"];    swl -- swn [label="12"];    swl -- swl [label="13"];    nwn -- nwl [label="14"];    nwl -- nwn [label="15"];    nwl -- swl [label="16"];    nwl -- snl [label="17"];    swl -- snl [label="18"];}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.