您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Go并發編程之死鎖與活鎖是什么”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Go并發編程之死鎖與活鎖是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
什么是死鎖:就是在并發程序中,兩個或多個線程彼此等待對方完成操作,從而導致它們都被阻塞,并無限期地等待對方完成。這種情況下,程序會卡死,無法繼續執行。
什么是活鎖:就是程序一直在運行,但是無法取得進展。例如,在某些情況下,多個線程會爭奪同一個資源,然后每個線程都會釋放資源,以便其他線程可以使用它。但是,如果沒有正確的同步,這些線程可能會同時嘗試獲取該資源,然后再次釋放它。這可能導致線程在無限循環中運行,卻無法取得進展。
1.編寫會發生死鎖的代碼:
package main import ( "fmt" "sync" ) func main() { var mu sync.Mutex mu.Lock() defer mu.Unlock() wg := sync.WaitGroup{} wg.Add(1) go func() { fmt.Println("goroutine started") mu.Lock() // 在這里獲取了鎖 fmt.Println("goroutine finished") mu.Unlock() wg.Done() }() wg.Wait() }
運行和輸出:
[root@workhost temp02]# go run main.go
goroutine started
fatal error: all goroutines are asleep - deadlock! # 錯誤很明顯了,告訴你死鎖啦!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000010030?)
/usr/local/go/src/runtime/sema.go:62 +0x27
...
...
上面的代碼,使用 sync.Mutex 實現了一個互斥鎖。主 goroutine 獲取了鎖,并啟動了一個新的 goroutine。新 goroutine 也嘗試獲取鎖來執行其任務。但是,由于主 goroutine 沒有釋放鎖,新 goroutine 將一直等待鎖,導致死鎖。
2.代碼改造
在上面的代碼中,可以通過將主 goroutine 中的 defer mu.Unlock() 移到 goroutine 函數中的 mu.Unlock() 后面來解決問題。這樣,當 goroutine 獲取到鎖后,它可以在完成任務后釋放鎖,以便主 goroutine 可以繼續執行。
改造后的代碼:
package main import ( "fmt" "sync" ) func main() { var mu sync.Mutex mu.Lock() wg := sync.WaitGroup{} wg.Add(1) go func() { fmt.Println("goroutine started") mu.Lock() // 在這里獲取了鎖 fmt.Println("goroutine finished") mu.Unlock() wg.Done() }() mu.Unlock() // 釋放鎖 wg.Wait() }
運行和輸出:
[root@workhost temp02]# go run main.go
goroutine started
goroutine finished
3.如何避免死鎖
在 Go 語言中,要避免死鎖,一定要清楚以下幾個規則:
避免嵌套鎖:在使用多個鎖時,確保它們的嵌套順序相同。否則,可能會出現循環等待的情況,導致死鎖。
避免無限等待:如果在獲取鎖時指定了超時時間,確保在超時后能夠處理錯誤或執行其他操作。
避免過度競爭:如果多個協程需要訪問相同的資源,請確保它們不會互相干擾。可以使用互斥鎖或讀寫鎖等機制來解決競爭問題。
使用通道:Go 語言中的通道可以用于協調并發操作。使用通道來傳遞消息和同步操作,可以避免死鎖和競爭問題。
確保資源釋放:在使用鎖或其他資源時,一定要確保它們在使用后得到釋放,否則可能會導致死鎖。
使用 select 語句:在使用通道進行并發操作時,可以使用 select 語句來避免死鎖。通過 select 語句選擇多個通道中的一個進行操作,可以避免在某個通道被阻塞時出現死鎖。
1.編寫會發生活鎖的代碼:
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup var mu sync.Mutex var flag bool wg.Add(2) // goroutine 1 go func() { // 先獲取鎖資源 fmt.Println("goroutine 1 獲取 mu") mu.Lock() defer mu.Unlock() // 然后等待 flag 變量的值變為 true fmt.Println("goroutine 1 等待標志") for !flag { // 不斷循環等待 } // 最終輸出并釋放鎖資源 fmt.Println("goroutine 1 從等待中釋放") wg.Done() }() // goroutine 2 go func() { // 先獲取鎖資源 fmt.Println("goroutine 2 獲取 mu") mu.Lock() defer mu.Unlock() // 然后等待 flag 變量的值變為 true fmt.Println("GoRoutine2 等待標志") for !flag { // 不斷循環等待 } // 最終輸出并釋放鎖資源 fmt.Println("GoRoutine 2 從等待中釋放") wg.Done() }() // 在主線程中等待 1 秒鐘,以便兩個 goroutine 開始等待 flag 變量的值 // 然后將 flag 變量設置為 true // 由于兩個 goroutine 會同時喚醒并嘗試獲取鎖資源,它們會相互等待 // 最終導致了活鎖問題,它們都無法向前推進 fmt.Println("主線程休眠 1 秒") fmt.Println("兩個goroutine都應該等待標志") flag = true wg.Wait() fmt.Println("所有 GoRoutines 已完成") }
運行和輸出:
[root@workhost temp02]# go run main.go
主線程休眠 1 秒
兩個goroutine都應該等待標志
goroutine 2 獲取 mu
GoRoutine2 等待標志
GoRoutine 2 從等待中釋放
goroutine 1 獲取 mu
goroutine 1 等待標志
goroutine 1 從等待中釋放
所有 GoRoutines 已完成
上面的代碼存在活鎖問題。如果兩個goroutine同時等待flag變為true并且都已經獲取了鎖資源,那么它們就會進入一個死循環并相互等待,無法繼續向前推進。
2.代碼改造
改造后的代碼:
package main import ( "fmt" "runtime" "sync" ) func main() { var wg sync.WaitGroup var mu sync.Mutex var flag bool wg.Add(2) // goroutine 1 go func() { // 先獲取鎖資源 fmt.Println("goroutine 1 獲取 mu") mu.Lock() defer mu.Unlock() // 然后等待 flag 變量的值變為 true fmt.Println("goroutine 1 等待標志") for !flag { runtime.Gosched() // 讓出時間片 } // 最終輸出并釋放鎖資源 fmt.Println("goroutine 1 從等待中釋放") wg.Done() }() // goroutine 2 go func() { // 先獲取鎖資源 fmt.Println("goroutine 2 獲取 mu") mu.Lock() defer mu.Unlock() // 然后等待 flag 變量的值變為 true fmt.Println("GoRoutine2 等待標志") for !flag { runtime.Gosched() // 讓出時間片 } // 最終輸出并釋放鎖資源 fmt.Println("GoRoutine 2 從等待中釋放") wg.Done() }() // 在主線程中等待 1 秒鐘,以便兩個 goroutine 開始等待 flag 變量的值 // 然后將 flag 變量設置為 true // 由于兩個 goroutine 會同時喚醒并嘗試獲取鎖資源,它們會相互等待 // 最終導致了活鎖問題,它們都無法向前推進 fmt.Println("主線程休眠 1 秒") fmt.Println("兩個goroutine都應該等待標志") flag = true wg.Wait() fmt.Println("所有 GoRoutines 已完成") }
改造后的代碼在等待flag變量的循環中加入了讓出時間片的函數 runtime.Gosched(),這樣兩個goroutine在等待期間可以放棄時間片,以便其他goroutine可以執行并獲得鎖資源。這種方式可以有效地減少競爭程度,從而避免了活鎖問題。
3.如何避免發生活鎖的可能性
在 Go 語言的并發編程中,避免活鎖的關鍵是正確地實現同步機制。以下是一些避免活鎖的方法:
避免忙等待:使用 sync.Cond 或者 channel 等同步機制來實現等待。這樣避免了線程一直占用 CPU 資源而無法取得進展的問題。
避免死鎖:死鎖往往是活鎖的前提,因此正確地使用鎖和同步機制可以避免死鎖,從而避免活鎖。
減少鎖的粒度:盡可能將鎖的粒度縮小到最小范圍,避免鎖住不必要的代碼塊。
采用超時機制:使用 sync.Mutex 的 TryLock() 方法或者使用 select 語句實現等待超時機制,這樣可以防止線程無限期等待。
合理設計并發模型:合理設計并發模型可以避免競爭和饑餓等問題,進而避免活鎖的發生。
讀到這里,這篇“Go并發編程之死鎖與活鎖是什么”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。