【每天一個Go知識點】(10)【轉(zhuǎn)】 go: 鎖的使用

版權(quán)聲明:本文為CSDN博主「程序員阿俊
」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/luolianxi/article/details/105195910


鎖的介紹與使用

1 互斥鎖

傳統(tǒng)并發(fā)程序?qū)蚕碣Y源進(jìn)行訪問控制的主要手段,由標(biāo)準(zhǔn)庫代碼包中sync中的Mutex結(jié)構(gòu)體表示。

//Mutex  是互斥鎖, 零值是解鎖的互斥鎖, 首次使用后不得復(fù)制互斥鎖。
type Mutex struct {
   state int32
   sema  uint32
}

sync.Mutex類型只有兩個公開的指針方法

//Locker表示可以鎖定和解鎖的對象。
type Locker interface {
   Lock()
   Unlock()
}
 
//鎖定當(dāng)前的互斥量
//如果鎖已被使用,則調(diào)用goroutine
//阻塞直到互斥鎖可用。
func (m *Mutex) Lock() 
 
//對當(dāng)前互斥量進(jìn)行解鎖
//如果在進(jìn)入解鎖時未鎖定m,則為運行時錯誤。
//鎖定的互斥鎖與特定的goroutine無關(guān)。
//允許一個goroutine鎖定Mutex然后安排另一個goroutine來解鎖它。
func (m *Mutex) Unlock()

聲明一個互斥鎖:

var mutex sync.Mutex

不像C或Java的鎖類工具,我們可能會犯一個錯誤:忘記及時解開已被鎖住的鎖,從而導(dǎo)致流程異常。但Go由于存在defer,所以此類問題出現(xiàn)的概率極低。關(guān)于defer解鎖的方式如下:

var mutex sync.Mutex
func Write()  {
   mutex.Lock()
   defer mutex.Unlock()
}

如果對一個已經(jīng)上鎖的對象再次上鎖,那么就會導(dǎo)致該鎖定操作被阻塞,直到該互斥鎖回到被解鎖狀態(tài)

func main()  {
   var mutex sync.Mutex
   fmt.Println("start lock main")
   mutex.Lock()
   fmt.Println("get locked main")
   for i := 1;i<=3 ;i++  {
      go func(i int) {  
         fmt.Println("start lock ",i)
         mutex.Lock()
         fmt.Println("get locked ",i)
      }(i)
   }
 
   time.Sleep(time.Second)
   fmt.Println("Unlock the lock main")
   mutex.Unlock()
   fmt.Println("get unlocked main")
   time.Sleep(time.Second)
}

上面的示例中,我們在for循環(huán)之前開始加鎖,然后在每一次循環(huán)中創(chuàng)建一個協(xié)程,并對其加鎖,但是由于之前已經(jīng)加鎖了,所以這個for循環(huán)中的加鎖會陷入阻塞直到main中的鎖被解鎖,time.Sleep(time.Second)是為了能讓系統(tǒng)有足夠的時間運行for循環(huán),最終在main解鎖后,三個協(xié)程會重新?lián)寠Z互斥鎖權(quán),最終協(xié)程3獲勝。
輸出結(jié)果如下:


互斥鎖鎖定操作的逆操作并不會導(dǎo)致協(xié)程阻塞,但是有可能導(dǎo)致引發(fā)一個無法恢復(fù)的運行時的panic,比如對一個未鎖定的互斥鎖進(jìn)行解鎖時就會發(fā)生panic。避免這種情況的最有效方式就是使用defer
我們知道如果遇到panic,可以使用recover方法進(jìn)行恢復(fù),但是如果對重復(fù)解鎖互斥鎖引發(fā)的panic卻是徒勞的(Go 1.8及以后)。

func main()  {
   defer func() {
      fmt.Println("Try to recover the panic")
      if p := recover();p!=nil{
         fmt.Println("recover the panic : ",p)
      }
   }()
   var mutex sync.Mutex
   fmt.Println("start lock")
   mutex.Lock()
   fmt.Println("get locked")
   fmt.Println("unlock lock")
   mutex.Unlock()
   fmt.Println("lock is unlocked")
   fmt.Println("unlock lock again")
   mutex.Unlock()
}

以上代碼試圖對重復(fù)解鎖引發(fā)的panic進(jìn)行recover,但是我們發(fā)現(xiàn)操作失敗,輸出結(jié)果:



雖然互斥鎖可以被多個協(xié)程共享,但還是建議將對同一個互斥鎖的加鎖解鎖操作放在同一個層次的代碼中。

2 讀寫鎖
讀寫鎖是針對讀寫操作的互斥鎖,可以分別針對讀操作與寫操作進(jìn)行鎖定和解鎖操作 。
讀寫鎖的訪問控制規(guī)則如下:
①多個寫操作之間是互斥的
②寫操作與讀操作之間也是互斥的
③多個讀操作之間不是互斥的
在這樣的控制規(guī)則下,讀寫鎖可以大大降低性能損耗。

// RWMutex是一個讀/寫互斥鎖,可以由任意數(shù)量的讀操作或單個寫操作持有。
// RWMutex的零值是未鎖定的互斥鎖。
//首次使用后,不得復(fù)制RWMutex。
//如果goroutine持有RWMutex進(jìn)行讀取而另一個goroutine可能會調(diào)用Lock,那么在釋放初始讀鎖之前,goroutine不應(yīng)該期望能夠獲取讀鎖定。 
//特別是,這種禁止遞歸讀鎖定。 這是為了確保鎖最終變得可用; 阻止的鎖定會阻止新讀操作獲取鎖定。
type RWMutex struct {
   w           Mutex  //如果有待處理的寫操作就持有
   writerSem   uint32 // 寫操作等待讀操作完成的信號量
   readerSem   uint32 //讀操作等待寫操作完成的信號量
   readerCount int32  // 待處理的讀操作數(shù)量
   readerWait  int32  // number of departing readers
}

sync中的RWMutex有以下幾種方法:

//對讀操作的鎖定
func (rw *RWMutex) RLock()
//對讀操作的解鎖
func (rw *RWMutex) RUnlock()
//對寫操作的鎖定
func (rw *RWMutex) Lock()
//對寫操作的解鎖
func (rw *RWMutex) Unlock()
 
//返回一個實現(xiàn)了sync.Locker接口類型的值,實際上是回調(diào)rw.RLock and rw.RUnlock.
func (rw *RWMutex) RLocker() Locker

Unlock會試圖喚醒所有因欲進(jìn)行讀鎖定而被阻塞的協(xié)程,而 RUnlock 只會在已無任何讀鎖定的情況下,試圖喚醒一個因欲進(jìn)行寫鎖定而被阻塞的協(xié)程。若對一個未被寫鎖定的讀寫鎖進(jìn)行寫解鎖,就會引發(fā)一個不可恢復(fù)的panic,同理對一個未被讀鎖定的讀寫鎖進(jìn)行讀寫鎖也會如此。

由于讀寫鎖控制下的多個讀操作之間不是互斥的,因此對于讀解鎖更容易被忽視。對于同一個讀寫鎖,添加多少個讀鎖定,就必要有等量的讀解鎖,這樣才能其他協(xié)程有機會進(jìn)行操作。

func main() {
   var rwm sync.RWMutex
   for i := 0; i < 3; i++ {
      go func(i int) {
         fmt.Println("try to lock read ", i)
         rwm.RLock()
         fmt.Println("get locked ", i)
         time.Sleep(time.Second *2)
         fmt.Println("try to unlock for reading ", i)
         rwm.RUnlock()
         fmt.Println("unlocked for reading ", i)
      }(i)
   }
   time.Sleep(time.Millisecond * 1000)
   fmt.Println("try to lock for writing")
   rwm.Lock()
   fmt.Println("locked for writing")
}

上面的示例創(chuàng)建了三個協(xié)程用于對讀寫鎖的讀鎖定與讀解鎖操作。在 rwm.Lock()種會對main中協(xié)程進(jìn)行寫鎖定,但是for循環(huán)中的讀解鎖尚未完成,因此會造成mian中的協(xié)程阻塞。當(dāng)for循環(huán)中的讀解鎖操作都完成后就會試圖喚醒main中阻塞的協(xié)程,main中的寫鎖定才會完成。輸出結(jié)果如下


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容