一文掌握Go語言并發(fā)編程必備的Mutex互斥鎖
在并發(fā)編程中,我們需要處理多個(gè)線程同時(shí)對共享資源的訪問問題。如果不加控制地同時(shí)訪問共享資源,就會導(dǎo)致競爭條件(Race Condition)問題,從而導(dǎo)致程序出現(xiàn)不可預(yù)知的錯(cuò)誤。為了解決這個(gè)問題,Go 語言提供了 sync 包,其中包括 Mutex 互斥鎖、RWMutex 讀寫鎖等同步機(jī)制,本篇博客將著重介紹 Mutex 互斥鎖的基本原理。
1. Mutex 互斥鎖的基本概念
Mutex 是 Mutual Exclusion(互斥)的縮寫,用于保護(hù)共享資源。當(dāng)一個(gè)goroutine 獲取了 Mutex 的鎖之后,其他的 goroutine 就無法再獲取到這個(gè) Mutex 的鎖,直到這個(gè) goroutine 釋放了這個(gè) Mutex 的鎖,其他 goroutine 才能繼續(xù)嘗試獲取這個(gè) Mutex 的鎖。
Mutex 互斥鎖包含兩個(gè)狀態(tài):鎖定和未鎖定。當(dāng)一個(gè) goroutine 獲取了 Mutex 的鎖,Mutex 就處于鎖定狀態(tài),其他的 goroutine 就只能等待這個(gè) goroutine 釋放這個(gè) Mutex 的鎖,才能再次嘗試獲取這個(gè) Mutex 的鎖。當(dāng)一個(gè) goroutine 釋放了 Mutex 的鎖,Mutex 就處于未鎖定狀態(tài),此時(shí)其他的 goroutine 可以獲取這個(gè) Mutex 的鎖。
Mutex 互斥鎖使用的是二進(jìn)制信號量的概念,當(dāng) Mutex 處于鎖定狀態(tài)時(shí),就相當(dāng)于信號量為 0,其他 goroutine 只能等待;當(dāng) Mutex 處于未鎖定狀態(tài)時(shí),就相當(dāng)于信號量為 1,其他 goroutine 可以嘗試獲取這個(gè) Mutex 的鎖。
2. Mutex 互斥鎖的基本用法
Mutex 互斥鎖的基本用法如下:
package main ? import ( "fmt" "sync" "time" ) ? func main() { var wg sync.WaitGroup var mu sync.Mutex var count int ? for i := 0; i < 10; i++ { wg.Add(1) go func() { mu.Lock() defer mu.Unlock() count++ fmt.Println(count) time.Sleep(time.Millisecond) wg.Done() }() } ? wg.Wait() }
在上面的例子中,我們創(chuàng)建了 10 個(gè) goroutine,每個(gè) goroutine 都會獲取 Mutex 的鎖,對共享變量 count 進(jìn)行自增操作,并打印 count 的值。在獲取 Mutex 的鎖后,我們使用 defer 語句來保證在 goroutine 結(jié)束時(shí)釋放 Mutex 的鎖。最后,我們使用 WaitGroup 來等待所有 goroutine 結(jié)束。
由于 Mutex 互斥鎖是排他性的,因此在同一時(shí)刻只有一個(gè) goroutine 可以獲取 Mutex 的鎖,其他的 goroutine 只能等待。在上面的例子中,我們通過 Mutex 互斥鎖來保證了 count 的原子性操作,從而避免了競爭條件問題。
3. Mutex 互斥鎖的底層實(shí)現(xiàn)
Mutex 互斥鎖的底層實(shí)現(xiàn)使用了操作系統(tǒng)提供的原語(Primitive),如互斥量(Mutex)、臨界區(qū)(Critical Section)等。在不同的操作系統(tǒng)中,Mutex 互斥鎖的底層實(shí)現(xiàn)可能有所不同。
在 Linux 系統(tǒng)中,Mutex 互斥鎖的底層實(shí)現(xiàn)主要使用了 futex(Fast User-space Mutex)機(jī)制。futex 是 Linux 系統(tǒng)提供的一種快速用戶空間互斥量機(jī)制,可以實(shí)現(xiàn)用戶空間的原子操作。
Mutex 互斥鎖的底層實(shí)現(xiàn)主要包括兩個(gè)部分:等待隊(duì)列和鎖狀態(tài)。
3.1 等待隊(duì)列
當(dāng)一個(gè) goroutine 嘗試獲取 Mutex 的鎖時(shí),如果 Mutex 已經(jīng)被其他 goroutine 獲取,那么這個(gè) goroutine 就會進(jìn)入等待隊(duì)列中等待。等待隊(duì)列是一個(gè)鏈表,每個(gè)節(jié)點(diǎn)代表一個(gè)等待 goroutine。
當(dāng)一個(gè) goroutine 釋放 Mutex 的鎖時(shí),會喚醒等待隊(duì)列中的第一個(gè) goroutine。喚醒的操作主要包括兩個(gè)步驟:
- 將等待隊(duì)列中的第一個(gè)節(jié)點(diǎn)從鏈表中移除,并將其狀態(tài)設(shè)置為可運(yùn)行(Runnable)狀態(tài)。
- 將移除的節(jié)點(diǎn)中的 goroutine 添加到調(diào)度器的可運(yùn)行隊(duì)列中,等待調(diào)度器將其調(diào)度執(zhí)行。
3.2 鎖狀態(tài)
Mutex 互斥鎖的鎖狀態(tài)主要包括兩個(gè)部分:互斥標(biāo)志和持有者標(biāo)志。
互斥標(biāo)志表示 Mutex 的狀態(tài),0 表示未鎖定,1 表示鎖定。互斥標(biāo)志的原子操作主要使用了 Compare-and-Swap(CAS)指令。
持有者標(biāo)志表示當(dāng)前持有 Mutex 的 goroutine 的 ID。如果 Mutex 未被任何 goroutine 持有,那么持有者標(biāo)志為 0。持有者標(biāo)志的原子操作主要使用了 Load Linked(LL)和 Store Conditional(SC)指令。
當(dāng)一個(gè) goroutine 嘗試獲取 Mutex 的鎖時(shí),會先嘗試使用 CAS 指令將互斥標(biāo)志從 0 改為 1。如果 CAS 指令成功,那么這個(gè) goroutine 就獲得了 Mutex 的鎖,并將持有者標(biāo)志設(shè)置為當(dāng)前 goroutine 的 ID。如果 CAS 指令失敗,那么說明 Mutex 已經(jīng)被其他 goroutine 獲取,這個(gè) goroutine 就會進(jìn)入等待隊(duì)列中等待。
當(dāng)一個(gè) goroutine 釋放 Mutex 的鎖時(shí),會先將持有者標(biāo)志設(shè)置為 0,然后再使用 LL 和 SC 指令將互斥標(biāo)志從 1 改為 0。LL 指令用于加載互斥標(biāo)志的值,SC 指令用于將互斥標(biāo)志的值改為 0。LL 和 SC 指令是原子指令,可以保證操作的原子性。
4. Mutex 互斥鎖的注意事項(xiàng)
在使用 Mutex 互斥鎖時(shí),需要注意以下幾點(diǎn):
4.1 不要將 Mutex 作為函數(shù)或方法的參數(shù)傳遞
Mutex 是一個(gè)結(jié)構(gòu)體類型,包含互斥標(biāo)志和持有者標(biāo)志等字段。當(dāng)將 Mutex 作為函數(shù)或方法的參數(shù)傳遞時(shí),會將 Mutex 的副本傳遞給函數(shù)或方法,而不是原始的 Mutex 實(shí)例。這樣做會導(dǎo)致不同的 goroutine 使用不同的 Mutex 實(shí)例,從而無法實(shí)現(xiàn)互斥。
正確的做法是將 Mutex 定義為一個(gè)全局變量,并在多個(gè) goroutine 中共享這個(gè)全局變量。
4.2 不要在獲取 Mutex 的鎖時(shí)阻塞太久
當(dāng)一個(gè) goroutine 嘗試獲取 Mutex 的鎖時(shí),如果 Mutex 已經(jīng)被其他 goroutine 獲取,那么這個(gè) goroutine 就會進(jìn)入等待隊(duì)列中等待。如果等待時(shí)間過長,會導(dǎo)致性能下降。
可以使用 TryLock() 方法嘗試獲取 Mutex 的鎖。TryLock() 方法會立即返回,如果獲取鎖成功返回 true,否則返回 false。
4.3 不要重復(fù)釋放 Mutex 的鎖
當(dāng)一個(gè) goroutine 釋放 Mutex 的鎖時(shí),如果這個(gè) goroutine 不是 Mutex 的持有者,那么會導(dǎo)致 panic 異常。因此,在釋放 Mutex 的鎖時(shí),需要確保當(dāng)前 goroutine 是 Mutex 的持有者。
可以使用 defer 語句在獲取 Mutex 的鎖時(shí)自動注冊釋放鎖的操作,以確保在任何情況下都能正確釋放 Mutex 的鎖。
4.4 不要在鎖內(nèi)部執(zhí)行阻塞或耗時(shí)操作
當(dāng)一個(gè) goroutine 持有 Mutex 的鎖時(shí),其他 goroutine 無法獲取 Mutex 的鎖,從而會導(dǎo)致阻塞。如果在 Mutex 的鎖內(nèi)部執(zhí)行阻塞或耗時(shí)操作,會導(dǎo)致其他 goroutine 長時(shí)間等待,從而影響性能。
可以將阻塞或耗時(shí)操作放到 Mutex 的鎖外部執(zhí)行,以避免阻塞其他 goroutine。
5. 總結(jié)
本文介紹了 Go 語言中的 Mutex 互斥鎖,包括 Mutex 的基本用法、互斥鎖的底層實(shí)現(xiàn)和注意事項(xiàng)。Mutex 是 Go 語言中實(shí)現(xiàn)互斥的重要工具,可以保證多個(gè) goroutine 之間的數(shù)據(jù)訪問安全。
在使用 Mutex 時(shí),需要注意避免一些常見的錯(cuò)誤,如將 Mutex 作為函數(shù)或方法的參數(shù)傳遞、在獲取 Mutex 的鎖時(shí)阻塞太久、重復(fù)釋放 Mutex 的鎖、在鎖內(nèi)部執(zhí)行阻塞或耗時(shí)操作等。
除了 Mutex 互斥鎖,Go 語言還提供了其他類型的鎖,如讀寫鎖(sync.RWMutex)、條件變量(sync.Cond)等,可以根據(jù)不同的場景選擇不同類型的鎖。
最后,希望你能夠通過本文對 Mutex 互斥鎖有一個(gè)更深的理解。
到此這篇關(guān)于一文掌握Go語言并發(fā)編程必備的Mutex互斥鎖的文章就介紹到這了,更多相關(guān)Go語言 Mutex互斥鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang的time包:秒、毫秒、納秒時(shí)間戳輸出方式
這篇文章主要介紹了golang的time包:秒、毫秒、納秒時(shí)間戳輸出方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go map底層實(shí)現(xiàn)與擴(kuò)容規(guī)則和特性分類詳細(xì)講解
這篇文章主要介紹了Go map底層實(shí)現(xiàn)與擴(kuò)容規(guī)則和特性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-03-03golang中判斷請求是http還是https獲取當(dāng)前訪問地址
這篇文章主要為大家介紹了golang中判斷請求是http還是https獲取當(dāng)前訪問地址示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10golang?pprof?監(jiān)控goroutine?thread統(tǒng)計(jì)原理詳解
這篇文章主要為大家介紹了golang?pprof?監(jiān)控goroutine?thread統(tǒng)計(jì)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04go編程中g(shù)o-sql-driver的離奇bug解決記錄分析
這篇文章主要為大家介紹了go編程中g(shù)o-sql-driver的離奇bug解決記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05