Go語言 | 併發設計中的同步鎖與waitgroup用法

,關注並星標,node





今天是golang專題的第16篇文章,咱們一塊兒來聊聊golang當中的併發相關的一些使用。golang


雖然關於goroutine以及channel咱們都已經介紹完了,可是關於併發的機制仍然沒有介紹結束。只有goroutine以及channel有時候仍是不足以完成咱們的問題,好比多個goroutine同時訪問一個變量的時候,咱們怎麼保證這些goroutine之間不會互相沖突或者是影響呢?這可能就須要咱們對資源進行加鎖或者是採起其餘的操做了。web

同步鎖


golang當中提供了兩種經常使用的鎖,一種是sync.Mutex另一種是sync.RWMutex。咱們先說說Mutex,它就是最簡單最基礎的同步鎖,當一個goroutine持有鎖的時候,其餘的goroutine只能等待到鎖釋放以後才能夠嘗試持有。而RWMutex是讀寫鎖的意思,它支持一寫多讀,也就是說容許支持多個goroutine同時持有讀鎖,而只容許一個goroutine持有寫鎖。當有goroutine持有讀鎖的時候,會阻止寫操做。當有goroutine持有寫鎖的時候,不管讀寫都會被堵塞。數據庫


咱們使用的時候須要根據咱們場景的特性來決定,若是咱們的場景是讀操做多過寫操做的場景,那麼咱們可使用RWMutex。若是是寫操做爲主,那麼使用哪一個都差很少。設計模式


咱們來看下使用的案例,假設咱們當前有多個goroutine,可是咱們只但願持有鎖的goroutine執行,咱們能夠這麼寫:api


var lock sync.Mutex

for i := 0; i < 10; i++ {
    go func() {
        lock.Lock()
        defer lock.Unlock()
        // do something
    }()
}


雖然咱們用for循環啓動了10個goroutine,可是因爲互斥鎖的存在,同一時刻只能有一個goroutine在執行微信


RWMutex區分了讀寫鎖,因此咱們一共會有4個api,分別是Lock, Unlock, RLock, RUnlock。Lock和Unlock是寫鎖的加鎖以及解鎖,而RLock和RUnlock天然就是讀鎖的加鎖和解鎖了。具體的用法和上面的代碼同樣,我就很少贅述了。併發


全局操做一次


在一些場景以及一些設計模式當中,會要求咱們某一段代碼只能執行一次。好比很著名的單例模式,就是將咱們常用的工具設計成單例,不管運行的過程中初始化多少次,獲得的都是同一個實例。這樣作的目的是減去建立實例的時間,尤爲是像是數據庫鏈接、hbase鏈接等這些實例建立的過程很是的耗時。app


那咱們怎麼在golang當中實現單例呢?編輯器


有些同窗可能會以爲這個很簡單啊,咱們只須要用一個bool型變量判斷一下初始化是否有完成不就能夠了嗎?好比這樣:


type Test struct {}
var test Test
var flag = false

func init() Test{
    if !flag {
        test = Test{}
        flag = true
    }
    return test
}


看起來好像沒有問題,可是仔細琢磨就會發現不對的地方。由於if判斷當中的語句並非原子的,也就是說有可能同時被不少goroutine同時訪問。這樣的話有可能test這個變量會被屢次初始化而且被屢次覆蓋,直到其中一個goroutine將flag置爲true爲止。這可能會致使一開始訪問的goroutine得到的test都各不相同,而產生未知的風險。


要想要實現單例其實很簡單,sync庫當中爲咱們提供了現成的工具once。它能夠傳入一個函數,只容許全局執行這個函數一次。在執行結束以前,其餘goroutine執行到once語句的時候會被阻塞,保證只有一個goroutine在執行once。當once執行結束以後,再次執行到這裏的時候,once語句的內容將會被跳過,咱們來結合一下代碼來理解一下,其實也很是簡單。


type Test struct {}
var test Test

func create() {
    test = Test{}
}

func init() Test{
    once.Do(create)
    return test
}

waitgroup


最後給你們介紹一下waitgroup的用法,咱們在使用goroutine的時候有一個問題是咱們在主程序當中並不知道goroutine執行結束的時間。若是咱們只是要依賴goroutine執行的結果,固然能夠經過channel來實現。但假如咱們明確地但願等到goroutine執行結束以後再執行下面的邏輯,這個時候咱們又該怎麼辦呢?


有人說能夠用sleep,但問題是咱們並不知道goroutine執行到底須要多少時間,怎麼能事先知道須要sleep多久呢?


爲了解決這個問題,咱們可使用sync當中的另一個工具,也就是waitgroup。


waitgroup的用法很是簡單,只有三個方法,一個是Add,一個是Done,最後一個是Wait。其實waitgroup內部存儲了當前有多少個goroutine在執行,當調用一次Add x的時候,表示當下同時產生了x個新的goroutine。當這些goroutine執行完的時候, 咱們讓它調用一下Done,表示執行結束了一個goroutine。這樣當全部goroutine都執行完Done以後,wait的阻塞會結束。


咱們來看一個例子:


sample := Sample{}

wg := sync.WaitGroup{}

go func() {
    // 增長一個正在執行的goroutine
    wg.Add(1)
    // 執行完成以後Done一下
    defer wg.Done()
    sample.JoinUserFeature() 
}()

go func() {
    wg.Add(1)
    defer wg.Done()
    sample.JoinItemFeature() 
}()

wg.Wait()
// do something

總結


上面介紹的這些工具和庫都是咱們平常在併發場景當中常用的,也是一個golang工程師必會的技能之一。到這裏爲止,關於golang這門語言的基本功能介紹就差很少了,後面將會介紹一些實際應用的內容,敬請期待吧。


今天的文章到這裏就結束了,若是喜歡本文的話,請來一波素質三連,給我一點支持吧(關注、在看、點贊)。

- END -


本文分享自微信公衆號 - TechFlow(techflow2019)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索