您好,登錄后才能下訂單哦!
本篇內容介紹了“Golang中的Mutex怎么使用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Mutex
是 Go
語言中互斥鎖的實現,它是一種同步機制,用于控制多個 goroutine
之間的并發訪問。當多個 goroutine
嘗試同時訪問同一個共享資源時,可能會導致數據競爭和其他并發問題,因此需要使用互斥鎖來協調它們之間的訪問。
在上述圖片中,我們可以將綠色部分看作是臨界區。當 g1
協程通過 mutex
對臨界區進行加鎖后,臨界區將會被鎖定。此時如果 g2
想要訪問臨界區,就會失敗并進入阻塞狀態,直到鎖被釋放,g2
才能拿到臨界區的訪問權。
type Mutex struct { state int32 sema uint32 }
字段:
state
state
是一個 int32
類型的變量,它存儲著 Mutex
的各種狀態信息(未加鎖、被加鎖、喚醒狀態、饑餓狀態),不同狀態通過位運算進行計算。
sema
sema
是一個信號量,用于實現 Mutex
的等待和喚醒機制。
方法:
Lock()
Lock()
方法用于獲取 Mutex
的鎖,如果 Mutex
已經被其他的 goroutine
鎖定,則 Lock()
方法會一直阻塞,直到該 goroutine
獲取到鎖為止。
UnLock()
Unlock()
方法用于釋放 Mutex
的鎖,將 Mutex
的狀態設置為未鎖定的狀態。
TryLock()
Go 1.18
版本以后,sync.Mutex
新增一個 TryLock()
方法,該方法為非阻塞式的加鎖操作,如果加鎖成功,返回 true
,否則返回 false
。
雖然 TryLock()
的用法確實存在,但由于其使用場景相對較少,因此在使用時應該格外謹慎。TryLock()
方法注釋如下所示:
// Note that while correct uses of TryLock do exist, they are rare, // and use of TryLock is often a sign of a deeper problem // in a particular use of mutexes.
我們先來看一個有并發安全問題的例子
package main import ( "fmt" "sync" ) var cnt int func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 10000; j++ { cnt++ } }() } wg.Wait() fmt.Println(cnt) }
在這個例子中,預期的 cnt
結果為 10 * 10000 = 100000
。但是由于多個 goroutine
并發訪問了共享變量 cnt
,并且沒有進行任何同步操作,可能導致讀寫沖突(race condition
),從而影響 cnt
的值和輸出結果的正確性。這種情況下,不能確定最終輸出的 cnt
值是多少,每次執行程序得到的結果可能不同。
在這種情況下,可以使用互斥鎖(sync.Mutex
)來保護共享變量的訪問,保證只有一個 goroutine
能夠同時訪問 cnt
,從而避免競態條件的問題。修改后的代碼如下:
package main import ( "fmt" "sync" ) var cnt int var mu sync.Mutex func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 10000; j++ { mu.Lock() cnt++ mu.Unlock() } }() } wg.Wait() fmt.Println(cnt) }
在這個修改后的版本中,使用互斥鎖來保護共享變量 cnt
的訪問,可以避免出現競態條件的問題。具體而言,在 cnt++
操作前,先執行 Lock()
方法,以確保當前 goroutine
獲取到了互斥鎖并且獨占了共享變量的訪問權。在 cnt++
操作完成后,再執行 Unlock()
方法來釋放互斥鎖,從而允許其他 goroutine
獲取互斥鎖并訪問共享變量。這樣,只有一個 goroutine
能夠同時訪問 cnt
,從而確保了最終輸出結果的正確性。
忘記解鎖
如果使用 Lock()
方法之后,沒有調用 Unlock()
解鎖,會導致其他 goroutine
被永久阻塞。例如:
package main import ( "fmt" "sync" "time" ) var mu sync.Mutex var cnt int func main() { go increase(1) go increase(2) time.Sleep(time.Second) fmt.Println(cnt) } func increase(delta int) { mu.Lock() cnt += delta }
在上述代碼中,通常情況下,cnt
的結果應該為 3
。然而沒有解鎖操作,其中一個 goroutine
被阻塞,導致沒有達到預期效果,最終輸出的 cnt
可能只能為 1
或 2
。
正確的做法是使用 defer
語句在函數返回前釋放鎖。
func increase(delta int) { mu.Lock() defer mu.Unlock() // 通過 defer 語句在函數返回前釋放鎖 cnt += delta }
重復加鎖
重復加鎖操作被稱為可重入操作。不同于其他一些編程語言的鎖實現(例如 Java
的 ReentrantLock
),Go
的 mutex
并不支持可重入操作,如果發生了重復加鎖操作,就會導致死鎖。例如:
package main import ( "fmt" "sync" "time" ) var mu sync.Mutex var cnt int func main() { go increase(1) go increase(2) time.Sleep(time.Second) fmt.Println(cnt) } func increase(delta int) { mu.Lock() mu.Lock() cnt += delta mu.Unlock() }
在這個例子中,如果在 increase
函數中重復加鎖,將會導致 mu
鎖被第二次鎖住,而其他 goroutine
將被永久阻塞,從而導致程序死鎖。正確的做法是只對需要加鎖的代碼段進行加鎖,避免重復加鎖。
import "sync" type Cache struct { data map[string]any mu sync.Mutex } func (c *Cache) Get(key string) (any, bool) { c.mu.Lock() defer c.mu.Unlock() value, ok := c.data[key] return value, ok } func (c *Cache) Set(key string, value any) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = value }
上述代碼實現了一個簡單的線程安全的緩存。使用 Mutex
可以保證同一時刻只有一個 goroutine
進行讀寫操作,避免多個 goroutine
并發讀寫同一數據時產生數據不一致性的問題。
對于緩存場景,讀操作比寫操作更頻繁,因此使用 RWMutex
代替 Mutex
會更好,因為 RWMutex
允許多個 goroutine
同時進行讀操作,只有在寫操作時才會進行互斥鎖定,從而減少了鎖的競爭,提高了程序的并發性能。
“Golang中的Mutex怎么使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。