點擊上方藍字,關注並星標,和我一起學技術。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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。