Go 互斥鎖(sync.Mutex)和 讀寫鎖(sync.RWMutex)


 

什么時候需要用到鎖?

當程序中就一個線程的時候,是不需要加鎖的,但是通常實際的代碼不會只是單線程,所以這個時候就需要用到鎖了,那么關於鎖的使用場景主要涉及到哪些呢?

  • 多個線程在讀相同的數據時
  • 多個線程在寫相同的數據時
  • 同一個資源,有讀又有寫

 

互斥鎖(sync.Mutex)

互斥鎖是一種常用的控制共享資源訪問的方法,它能夠保證同時只有一個 goroutine 可以訪問到共享資源(同一個時刻只有一個線程能夠拿到鎖

先通過一個並發讀寫的例子演示一下,當多線程同時訪問全局變量時,結果會怎樣?

package main
import ("fmt")

var count int

func main() {
	for i := 0; i < 2; i++ {
		go func() {
			for i := 1000000; i > 0; i-- {
				count ++
			}
			fmt.Println(count)
		}()
	}

	fmt.Scanf("\n")  //等待子線程全部結束
}

運行結果:
980117
1011352  //最后的結果基本不可能是我們想看到的:200000

 

修改代碼,在累加的地方添加互斥鎖,就能保證我們每次得到的結果都是想要的值

package main
import ("fmt"
    "sync"
)

var (
    count int
    lock sync.Mutex
)

func main() {
    for i := 0; i < 2; i++ {
        go func() {
            for i := 1000000; i > 0; i-- {
                lock.Lock()
                count ++
                lock.Unlock()
            }
            fmt.Println(count)
        }()
    }

    fmt.Scanf("\n")  //等待子線程全部結束
}

運行結果:
1952533
2000000  //最后的線程打印輸出
View Code

 

讀寫鎖(sync.RWMutex)

在讀多寫少的環境中,可以優先使用讀寫互斥鎖(sync.RWMutex),它比互斥鎖更加高效。sync 包中的 RWMutex 提供了讀寫互斥鎖的封裝

讀寫鎖分為:讀鎖和寫鎖

  • 如果設置了一個寫鎖,那么其它讀的線程以及寫的線程都拿不到鎖,這個時候,與互斥鎖的功能相同
  • 如果設置了一個讀鎖,那么其它寫的線程是拿不到鎖的,但是其它讀的線程是可以拿到鎖

 

通過設置寫鎖,同樣可以實現數據的一致性:

package main
import ("fmt"
	"sync"
)

var (
	count int
	rwLock sync.RWMutex
)

func main() {
	for i := 0; i < 2; i++ {
		go func() {
			for i := 1000000; i > 0; i-- {
				rwLock.Lock()
				count ++
				rwLock.Unlock()
			}
			fmt.Println(count)
		}()
	}

	fmt.Scanf("\n")  //等待子線程全部結束
}

運行結果:
1968637
2000000

 

互斥鎖和讀寫鎖的性能對比

demo:制作一個讀多寫少的例子,分別開啟 3 個 goroutine 進行讀和寫,輸出最終的讀寫次數

1)使用互斥鎖:

package main
import (
	"fmt"
	"sync"
	"time"
)

var (
	count  int
	//互斥鎖
	countGuard sync.Mutex
)

func read(mapA map[string]string){
	for {
		countGuard.Lock()
		var _ string = mapA["name"]
		count += 1
		countGuard.Unlock()
	}
}

func write(mapA map[string]string) {
	for {
		countGuard.Lock()
		mapA["name"] = "johny"
		count += 1
		time.Sleep(time.Millisecond * 3)
		countGuard.Unlock()
	}
}



func main() {
	var num int = 3
	var mapA map[string]string = map[string]string{"nema": ""}

	for i := 0; i < num; i++ {
		go read(mapA)
	}

	for i := 0; i < num; i++ {
		go write(mapA)
	}

	time.Sleep(time.Second * 3)
	fmt.Printf("最終讀寫次數:%d\n", count)
}

運行結果:
最終讀寫次數:3766

 

2)使用讀寫鎖

package main
import (
	"fmt"
	"sync"
	"time"
)

var (
	count  int
	//讀寫鎖
	countGuard sync.RWMutex
)

func read(mapA map[string]string){
	for {
		countGuard.RLock()  //這里定義了一個讀鎖
		var _ string = mapA["name"]
		count += 1
		countGuard.RUnlock()
	}
}

func write(mapA map[string]string) {
	for {
		countGuard.Lock()  //這里定義了一個寫鎖
		mapA["name"] = "johny"
		count += 1
		time.Sleep(time.Millisecond * 3)
		countGuard.Unlock()
	}
}



func main() {
	var num int = 3
	var mapA map[string]string = map[string]string{"nema": ""}

	for i := 0; i < num; i++ {
		go read(mapA)
	}

	for i := 0; i < num; i++ {
		go write(mapA)
	}

	time.Sleep(time.Second * 3)
	fmt.Printf("最終讀寫次數:%d\n", count)
}

運行結果:
最終讀寫次數:8165

結果差距大概在 2 倍左右,讀鎖的效率要快很多!

 

關於互斥鎖的補充

互斥鎖需要注意的問題:

  1. 不要重復鎖定互斥鎖
  2. 不要忘記解鎖互斥鎖,必要時使用 defer 語句
  3. 不要在多個函數之間直接傳遞互斥鎖

 

死鎖: 當前程序中的主 goroutine 以及我們啟用的那些 goroutine 都已經被阻塞,這些 goroutine 可以被稱為用戶級的 goroutine 這就相當於整個程序已經停滯不前了,並且這個時候 go 程序會拋出如下的 panic:

fatal error: all goroutines are asleep - deadlock!

並且go語言運行時系統拋出自行拋出的panic都屬於致命性錯誤,都是無法被恢復的,調用recover函數對他們起不到任何作用

Go語言中的互斥鎖是開箱即用的,也就是我們聲明一個sync.Mutex 類型的變量,就可以直接使用它了,需要注意:該類型是一個結構體類型,屬於值類型的一種,將它當做參數傳給一個函數,將它從函數中返回,把它賦值給其他變量,讓它進入某個管道,都會導致他的副本的產生。並且原值和副本以及多個副本之間是完全獨立的,他們都是不同的互斥鎖,所以不應該將鎖通過函數的參數進行傳遞

 

關於讀寫鎖的補充

1、在寫鎖已被鎖定的情況下再次試圖鎖定寫鎖,會阻塞當前的goroutine

2、在寫鎖已被鎖定的情況下再次試圖鎖定讀鎖,也會阻塞當前的goroutine

3、在讀鎖已被鎖定的情況下試圖鎖定寫鎖,同樣會阻塞當前的goroutine

4、在讀鎖已被鎖定的情況下再試圖鎖定讀鎖,並不會阻塞當前的goroutine

 

對於某個受到讀寫鎖保護的共享資源,多個寫操作不能同時進行,寫操作和讀操作也不能同時進行,但多個讀操作卻可以同時進行

對寫鎖進行解鎖,會喚醒“所有因試圖鎖定讀鎖,而被阻塞的goroutine”, 並且這個通常會使他們都成功完成對讀鎖的鎖定(這個還不理解)

對讀鎖進行解鎖,只會在沒有其他讀鎖鎖定的前提下,喚醒“因試圖鎖定寫鎖,而被阻塞的 goroutine” 並且只會有一個被喚醒的 goroutine 能夠成功完成對寫鎖的鎖定,其他的 goroutine 還要在原處繼續等待,至於哪一個goroutine,那么就要看誰等待的事件最長

解鎖讀寫鎖中未被鎖定的寫鎖, 會立即引發panic ,對其中的讀鎖也是如此,並且同樣是不可恢復的

 

參考鏈接:https://www.cnblogs.com/zhaof/p/8636384.html

 

 

ending ~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM