日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Go并發同步Mutex典型易錯使用場景_Golang

作者:BeCautious ? 更新時間: 2022-09-26 編程語言

Mutex的4種易錯使用場景

1.Lock/Unlock 不成對出現

Lock/Unlock 沒有成對出現,就可能會出現死鎖或者是因為Unlock一個未加鎖的Mutex而導致 panic。

忘記Unlock的情形

  • 代碼中有太多的 if-else 分支,可能在某個分支中漏寫了 Unlock;
  • 在重構的時候把 Unlock 給刪除了;
  • Unlock 誤寫成了 Lock。

忘記Lock的情形一般是誤刪除了或者注釋掉了Lock。

eg:

func main() {
   var mu sync.Mutex
   defer mu.Unlock()
   fmt.Println("oh, missing Lock!")
}

error result:

2.Copy 已使用的 Mutex

實際上sync包下的同步原語在使用后都是不可復制的,原因在于Mutex是有狀態的,其state的值時刻在變化,如果復制一個已經加鎖的Metux對象給一個新的變量,可能這個變量剛初始化就顯示被加鎖了,這顯然是不合理的。

eg:以下代碼在調用 foo 函數的時候,調用者會復制 Mutex 變量 c 作為 foo 函數的參數,不幸的是,復制之前已經使用了這個鎖,這就導致,復制的 Counter 是一個帶狀態 Counter,從而會導致死鎖。

type Counter struct {
   sync.Mutex
   Count int
}
func main() {
   var c Counter
   c.Lock()
   defer c.Unlock()
   c.Count++
   foo(c) // 復制鎖
}
// 這里Counter的參數是通過復制的方式傳入的
func foo(c Counter) {
   c.Lock()
   defer c.Unlock()
   fmt.Println("in foo")
}

error result:還好有Go的協程死鎖檢查機制,程序運行后會快速失敗而不是一直hang住。

Go Vet指令

我們當然不想程序運行了才發現死鎖,我們可以通過go vet指令來在運行前檢查我們的代碼是否存在lock copy問題:

檢查原理

檢查是通過copylock分析器靜態分析實現的。這個分析器會分析函數調用、range 遍歷、復制、聲明、函數返回值等位置,有沒有鎖的值 copy 的情景,以此來判斷有沒有問題。

通過源碼我們可以看到實現了Lock或者Unlock接口的struct都支持copylock檢查。

var lockerType *types.Interface
 // Construct a sync.Locker interface type.
 func init() {
     nullary := types.NewSignature(nil, nil, nil, false) // func()
     methods := []*types.Func{
     types.NewFunc(token.NoPos, nil, "Lock", nullary),
     types.NewFunc(token.NoPos, nil, "Unlock", nullary),
 }
 lockerType = types.NewInterface(methods, nil).Complete()
 }

3.重入

Mutex不像Java中的ReentrantLock擁有可重入的功能,主要是因為其實現中沒有標記位記錄哪個goroutine 擁有這把鎖,所以Mutex是一個不可重入鎖,而一旦誤用Mutex的重入就會報錯。

eg:

func foo(l sync.Locker) {
   fmt.Println("in foo")
   l.Lock()
   bar(l)
   l.Unlock()
}
func bar(l sync.Locker) {
   l.Lock()
   fmt.Println("in bar")
   l.Unlock()
}
func main() {
   l := &sync.Mutex{}
   foo(l)
}

error result:我們可以看到當在bar方法中嘗試再次獲取鎖時,獲取不到,觸發了死鎖。

4.死鎖

兩個或兩個以上的進程(或線程,goroutine)

執行過程中,因爭奪共享資源而處于一種互相等待的狀態,如果沒有外部干涉,它們都將無法推進下去,此時,我們稱系統處于死鎖狀態或系統產生了死鎖。

死鎖產生的4個必要條件

如果想避免死鎖,我們只要思考如何打破以下任意條件就可以。

  • 1.互斥: 至少一個資源是被排他性獨享的,其他線程必須處于等待狀態,直到資源被釋放。
  • 2.持有和等待:goroutine 持有一個資源,并且還在請求其它 goroutine 持有的資源,也就是咱們常說的“吃著碗里,看著鍋里”的意思。
  • 3. 不可剝奪:資源只能由持有它的 goroutine 來釋放。
  • 4.環路等待:一般來說,存在一組等待進程,P={P1,P2,…,PN},P1 等待 P2 持有的

資源,P2 等待 P3 持有的資源,依此類推,最后是 PN 等待 P1 持有的資源,這就形成
了一個環路等待的死結。

eg:在這里我們以辦理居住證業務,舉一個簡單的環路等待導致死鎖的例子:

//辦理居住證
func main() {
   // 網簽中心證明
   var psCertificate sync.Mutex
   // 社區證明
   var propertyCertificate sync.Mutex
   var wg sync.WaitGroup
   wg.Add(2) // 需要網簽中心和社區都處理
   // 網簽中心處理goroutine
   go func() {
      defer wg.Done() // 網簽中心處理完成
      psCertificate.Lock()
      defer psCertificate.Unlock()
      // 檢查材料
      time.Sleep(5 * time.Second)
      // 請求社區的證明
      propertyCertificate.Lock()
      propertyCertificate.Unlock()
   }()
   // 社區處理goroutine
   go func() {
      defer wg.Done() // 社區處理完成
      propertyCertificate.Lock()
      defer propertyCertificate.Unlock()
      // 檢查材料
      time.Sleep(5 * time.Second)
      // 請求網簽中心的證明
      psCertificate.Lock()
      psCertificate.Unlock()
   }()
   wg.Wait()
   fmt.Println("成功完成")
}

error result:

解決策略

1.可以引入一個第三方的鎖,大家都依賴這個鎖進行業務處理,比如現在政府推行的一站式政務服務中心。

2.解決持有等待問題,比如社區不需要看到網簽中心的證明才給開居住證明。

原文鏈接:https://juejin.cn/post/7101531692698959886

欄目分類
最近更新