在去學(xué)習(xí)go語(yǔ)言鎖機(jī)制的時(shí)候讶舰,我會(huì)問(wèn)自己幾個(gè)問(wèn)題:
1.鎖是什么 鞍盗,為什么要用鎖需了?
2.都有哪些鎖怎么用?
3.出現(xiàn)問(wèn)題了怎么辦般甲?
4.如何抉擇和調(diào)優(yōu)肋乍?
鎖是什么 ,為什么要用鎖敷存?
在解釋什么是鎖之前墓造,我們先了解下什么樣的場(chǎng)景需要使用到鎖,鎖用于解決什么問(wèn)題锚烦。
Go 語(yǔ)言宣揚(yáng)的“用通訊的方式共享數(shù)據(jù)”,用句白話說(shuō)就是可以用channel來(lái)實(shí)現(xiàn)鎖的功能.但是通過(guò)共享數(shù)據(jù)的方式來(lái)傳遞信息和協(xié)調(diào)線程運(yùn)行的做法其實(shí)更加主流觅闽。go依然提供了很多傳統(tǒng)的并發(fā)控制api,這些東西基本都在sync包中涮俄。
競(jìng)態(tài)條件
用共享數(shù)據(jù)的方式來(lái)傳遞信息勢(shì)必會(huì)面臨一個(gè)問(wèn)題蛉拙,一旦數(shù)據(jù)被多個(gè)線程共享,那么就很可能會(huì)產(chǎn)生爭(zhēng)用和沖突的情況彻亲。這種情況也被稱為競(jìng)態(tài)條件(race condition)孕锄。
數(shù)據(jù)的一致性
競(jìng)態(tài)條件往往會(huì)破會(huì)共享數(shù)據(jù)的一致性。
共享數(shù)據(jù)的一致性代表著某種約定苞尝,即:多個(gè)線程對(duì)共享數(shù)據(jù)的操作總是可以達(dá)到它們各自預(yù)期的效果畸肆。
如果這個(gè)一致性得不到保證,那么將會(huì)影響到一些線程中代碼和流程的正確執(zhí)行宙址,甚至?xí)斐赡撤N不可預(yù)知的錯(cuò)誤轴脐。這種錯(cuò)誤一般都很難發(fā)現(xiàn)和定位,排查起來(lái)的成本也是非常高的抡砂,所以一定要盡量避免大咱。
例子:
舉個(gè)例子,同時(shí)有多個(gè)線程連續(xù)向同一個(gè)緩沖區(qū)寫(xiě)入數(shù)據(jù)塊舀患,如果沒(méi)有一個(gè)機(jī)制去協(xié)調(diào)這些線程的寫(xiě)入操作的話徽级,那么被寫(xiě)入的數(shù)據(jù)塊就很可能會(huì)出現(xiàn)錯(cuò)亂。比如聊浅,在線程A還沒(méi)有寫(xiě)完一個(gè)數(shù)據(jù)塊的時(shí)候餐抢,線程B就開(kāi)始寫(xiě)入另外一個(gè)數(shù)據(jù)塊了。顯然低匙,這兩個(gè)數(shù)據(jù)塊中的數(shù)據(jù)會(huì)被混在一起旷痕,并且已經(jīng)很難分清了。因此顽冶,在這種情況下欺抗,我們就需要采取一些措施來(lái)協(xié)調(diào)它們對(duì)緩沖區(qū)的修改。這通常就會(huì)涉及同步强重。
概括來(lái)講绞呈,同步的用途有兩個(gè)贸人,一個(gè)是避免多個(gè)線程在同一時(shí)刻操作同一個(gè)數(shù)據(jù)塊,另一個(gè)是協(xié)調(diào)多個(gè)線程佃声,以避免它們?cè)谕粫r(shí)刻執(zhí)行同一個(gè)代碼塊艺智。
demo1
var i []int
var lock *sync.Mutex
func init() {
lock = &sync.Mutex{}
i = make([]int, 1)
}
func A() {
update("A", 1)
}
func B() {
update("B", 2)
}
func update(name string, v int) {
//lock.Lock()
//defer lock.Unlock()
i[0] = v
time.Sleep(1000)
fmt.Printf("%s-%d\n", name, i[0])
}
func MutexTest1() {
for i := 0; i < 10; i++ {
go A()
}
for i := 0; i < 10; i++ {
go B()
}
}
期望輸出的結(jié)果是:
A-1
B-2
實(shí)際輸出的結(jié)果:
A-1, A-2 ,B-1, B-2 都是有可能。
這顯然與他們各自的預(yù)期結(jié)果是不符合圾亏。
臨界區(qū)(critical section)
上述例子中的‘i’就是一個(gè)共享資源十拣,一個(gè)線程在想要訪問(wèn)某一個(gè)共享資源的時(shí)候,需要先申請(qǐng)對(duì)該資源的訪問(wèn)權(quán)限志鹃,并且只有在申請(qǐng)成功之后夭问,訪問(wèn)才能真正開(kāi)始。而當(dāng)線程對(duì)共享資源的訪問(wèn)結(jié)束時(shí)曹铃,它還必須歸還對(duì)該資源的訪問(wèn)權(quán)限缰趋,若要再次訪問(wèn)仍需申請(qǐng)。你可以把這里所說(shuō)的訪問(wèn)權(quán)限想象成一塊令牌陕见,線程一旦拿到了令牌埠胖,就可以進(jìn)入指定的區(qū)域,從而訪問(wèn)到資源淳玩,而一旦線程要離開(kāi)這個(gè)區(qū)域了,就需要把令牌還回去非竿,絕不能把令牌帶走蜕着。如果針對(duì)某個(gè)共享資源的訪問(wèn)令牌只有一塊,那么在同一時(shí)刻红柱,就最多只能有一個(gè)線程進(jìn)入到那個(gè)區(qū)域承匣,并訪問(wèn)到該資源。這時(shí)锤悄,我們可以說(shuō)韧骗,多個(gè)并發(fā)運(yùn)行的線程對(duì)這個(gè)共享資源的訪問(wèn)是完全串行的。只要一個(gè)代碼片段需要實(shí)現(xiàn)對(duì)共享資源的串行化訪問(wèn)零聚,就可以被視為一個(gè)臨界區(qū)(critical section)袍暴,也就是我剛剛說(shuō)的,由于要訪問(wèn)到資源而必須進(jìn)入的那個(gè)區(qū)域隶症。如果針對(duì)同一個(gè)共享資源政模,這樣的代碼片段有多個(gè),那么它們就可以被稱為相關(guān)臨界區(qū)蚂会。
同步工具
臨界區(qū)總是需要受到保護(hù)的淋样,否則就會(huì)產(chǎn)生競(jìng)態(tài)條件。施加保護(hù)的重要手段之一胁住,就是使用實(shí)現(xiàn)了某種同步機(jī)制的工具趁猴,也稱為同步工具刊咳。在Go語(yǔ)言中,可供我們選擇的同步工具并不少儡司。其中娱挨,最重要且最常用的同步工具當(dāng)屬互斥量(mutual exclusion,簡(jiǎn)稱mutex)枫慷。sync包中的Mutex就是與其對(duì)應(yīng)的類型让蕾,該類型的值可以被稱為互斥量或者互斥鎖。
所以說(shuō)鎖其實(shí)是一種同步工具或听,能消除競(jìng)態(tài)條件探孝,保護(hù)共享數(shù)據(jù)的數(shù)據(jù)的一致性。
都有哪些鎖誉裆,怎么用顿颅?
go語(yǔ)言的sync包下實(shí)現(xiàn)了兩種鎖。sync.Mutex (互斥鎖)和 sync.RWMutex(讀寫(xiě)鎖)足丢。
sync.Cond有些地方稱為條件變量粱腻,有些稱為條件鎖。這個(gè)看你怎么理解了斩跌。
sync.Mutex (互斥鎖mutual exclusion绍些,簡(jiǎn)稱mutex)
如demo1
func update(name string, v int) {
lock.Lock()
defer lock.Unlock()
i[0] = v
time.Sleep(1000)
fmt.Printf("%s-%d\n", name, i[0])
}
一個(gè)互斥鎖可以被用來(lái)保護(hù)一個(gè)臨界區(qū)或者一組相關(guān)臨界區(qū)。我們可以通過(guò)它來(lái)保證耀鸦,在同一時(shí)刻只有一個(gè)goroutine處于該臨界區(qū)之內(nèi)柬批。為了兌現(xiàn)這個(gè)保證,每當(dāng)有g(shù)oroutine想進(jìn)入臨界區(qū)時(shí)袖订,都需要先對(duì)它進(jìn)行鎖定氮帐,并且,每個(gè)goroutine離開(kāi)臨界區(qū)時(shí)洛姑,都要及時(shí)地對(duì)它進(jìn)行解鎖上沐。
鎖定操作可以通過(guò)調(diào)用互斥鎖的Lock方法實(shí)現(xiàn),而解鎖操作可以調(diào)用互斥鎖的Unlock方法楞艾。
使用互斥鎖的注意事項(xiàng)如下:
1.不要重復(fù)鎖定互斥鎖参咙;(避免死鎖)
2.不要忘記解鎖互斥鎖,必要時(shí)使用defer語(yǔ)句产徊;(防止忘記解鎖)
3.不要對(duì)尚未鎖定或者已解鎖的互斥鎖解鎖昂勒;(會(huì)panic)
4.不要在多個(gè)函數(shù)之間直接傳遞互斥鎖。(順序和鎖的次數(shù)有錯(cuò)亂的可能造成死鎖)
死鎖
所謂的死鎖舟铜,指的就是當(dāng)前程序中的主goroutine戈盈,以及我們啟用的那些goroutine都已經(jīng)被阻塞。這些goroutine可以被統(tǒng)稱為用戶級(jí)的goroutine。這就相當(dāng)于整個(gè)程序都已經(jīng)停滯不前了塘娶。Go語(yǔ)言運(yùn)行時(shí)系統(tǒng)是不允許這種情況出現(xiàn)的(目前還沒(méi)接觸過(guò)允許死鎖的語(yǔ)言)归斤,只要它發(fā)現(xiàn)所有的用戶級(jí)goroutine都處于等待狀態(tài),就會(huì)自行拋出一個(gè)帶有如下信息的panic:
fatal error: all goroutines are asleep - deadlock!
注意刁岸,這種由Go語(yǔ)言運(yùn)行時(shí)系統(tǒng)自行拋出的panic都屬于致命錯(cuò)誤脏里,都是無(wú)法被恢復(fù)的,調(diào)用recover函數(shù)對(duì)它們起不到任何作用虹曙。也就是說(shuō)迫横,一旦產(chǎn)生死鎖,程序必然崩潰酝碳。