亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang?Mutex錯過會后悔的重要知識點分享

 更新時間:2023年07月26日 10:18:13   作者:碼一行  
互斥鎖?Mutex?是并發(fā)控制的一個基本手段,是為了避免并發(fā)競爭建立的并發(fā)控制機制,本文主要為大家整理了一些Mutex的相關知識點,希望對大家有所幫助

Go Mutex 的基本用法

Mutex 我們一般只會用到它的兩個方法:

  • Lock:獲取互斥鎖。(只會有一個協(xié)程可以獲取到鎖,通常用在臨界區(qū)開始的地方。)
  • Unlock: 釋放互斥鎖。(釋放獲取到的鎖,通常用在臨界區(qū)結束的地方。)

Mutex 的模型可以用下圖表示:

說明:

  • 同一時刻只能有一個協(xié)程獲取到 Mutex 的使用權,其他協(xié)程需要排隊等待(也就是上圖的 G1->G2->Gn)。
  • 擁有鎖的協(xié)程從臨界區(qū)退出的時候需要使用 Unlock 來釋放鎖,這個時候等待隊列的下一個協(xié)程可以獲取到鎖(實際實現(xiàn)比這里說的復雜很多,后面會細說),從而進入臨界區(qū)。
  • 等待的協(xié)程會在 Lock 調用處阻塞,Unlock 的時候會使得一個等待的協(xié)程解除阻塞的狀態(tài),得以繼續(xù)執(zhí)行。

這幾點也是 Mutex 的基本原理。

Go Mutex 原子操作

Mutex結構體定義:

type Mutex struct {
   state int32 // 狀態(tài)字段
   sema  uint32 // 信號量
}

其中 state 字段記錄了四種不同的信息:

這四種不同信息在源碼中定義了不同的常量:

const (
   mutexLocked      = 1 << iota // 表示有 goroutine 擁有鎖
   mutexWoken                   // 喚醒(就是第 2 位)
   mutexStarving                // 饑餓(第 3 位)
   mutexWaiterShift = iota      // 表示第 4 位開始,表示等待者的數(shù)量
   starvationThresholdNs = 1e6  // 1ms 進入饑餓模式的等待時間閾值
)

而 sema 的含義比較簡單,就是一個用作不同 goroutine 同步的信號量。

go 的 Mutex 實現(xiàn)中,state 字段是一個 32 位的整數(shù),不同的位記錄了四種不同信息,在這種情況下, 只需要通過原子操作就可以保證一次性實現(xiàn)對四種不同狀態(tài)信息的更改,而不需要更多額外的同步機制。

但是毋庸置疑,這種實現(xiàn)會大大降低代碼的可讀性,因為通過一個整數(shù)來記錄不同的信息, 就意味著,需要通過各種位運算來實現(xiàn)對這個整數(shù)不同位的修改。

當然,這只是 Mutex 實現(xiàn)中最簡單的一種位運算了。下面以 state 記錄的四種不同信息為維度來具體講解一下:

mutexLocked:這是 state 的最低位,1 表示鎖被占用,0 表示鎖沒有被占用。

new := mutexLocked 新狀態(tài)為上鎖狀態(tài)

mutexWoken: 這是表示是否有協(xié)程被喚醒了的狀態(tài)

  • new = (old - 1<<mutexWaiterShift) | mutexWoken 等待者數(shù)量減去 1 的同時,設置喚醒標識
  • new &^= mutexWoken 清除喚醒標識

mutexStarving:饑餓模式的標識

new |= mutexStarving 設置饑餓標識

等待者數(shù)量:state >> mutexWaiterShift 就是等待者的數(shù)量,也就是上面提到的 FIFO 隊列中 goroutine 的數(shù)量

  • new += 1 << mutexWaiterShift 等待者數(shù)量加 1
  • delta := int32(mutexLocked - 1<<mutexWaiterShift) 上鎖的同時,將等待者數(shù)量減 1

在上面做了這一系列的位運算之后,我們會得到一個新的 state 狀態(tài),假設名為 new,那么我們就可以通過 CAS 操作來將 Mutex 的 state 字段更新:

atomic.CompareAndSwapInt32(&m.state, old, new)

通過上面這個原子操作,我們就可以一次性地更新 Mutex 的 state 字段,也就是一次性更新了四種狀態(tài)信息。

這種通過一個整數(shù)記錄不同狀態(tài)的寫法在 sync 包其他的一些地方也有用到,比如 WaitGroup 中的 state 字段。

最后,對于這種操作,我們需要注意的是,因為我們在執(zhí)行 CAS 前后是沒有其他什么鎖或者其他的保護機制的, 這也就意味著上面的這個 CAS 操作是有可能會失敗的,那如果失敗了怎么辦呢?

如果失敗了,也就意味著肯定有另外一個 goroutine 率先執(zhí)行了 CAS 操作并且成功了,將 state 修改為了一個新的值。 這個時候,其實我們前面做的一系列位運算得到的結果實際上已經(jīng)不對了,在這種情況下,我們需要獲取最新的 state,然后再次計算得到一個新的 state。

所以我們會在源碼里面看到 CAS 操作是寫在 for 循環(huán)里面的。

state的狀態(tài)及枚舉

state狀態(tài)state狀態(tài)枚舉對應二進制對應狀態(tài)
mutexUnLockstate=00000未加鎖
mutexLockedstate=10001加鎖
mutexWokenstate=20010喚醒
mutexStarvingstate=40100饑餓
mutexWaiterShiftstate=30011代表位移

在看下面代碼之前,一定要記住這幾個狀態(tài)之間的 與運算 或運算,否則代碼里的與運算或運算

state:   |32|31|...|3|2|1|
         __________/ | |
               |      | |
               |      | mutex的占用狀態(tài)(1被占用,0可用)
               |      |
               |      mutex的當前goroutine是否被喚醒
               |
               當前阻塞在mutex上的goroutine數(shù)

互斥鎖的作用

互斥鎖是保證同步的一種工具,主要體現(xiàn)在以下2個方面:

避免多個線程在同一時刻操作同一個數(shù)據(jù)塊 (sum)

可以協(xié)調多個線程,以避免它們在同一時刻執(zhí)行同一個代碼塊 (sum++)

什么時候用

需要保護一個數(shù)據(jù)或數(shù)據(jù)塊時

需要協(xié)調多個協(xié)程串行執(zhí)行同一代碼塊,避免并發(fā)問題時

比如 經(jīng)常遇到A給B轉賬100元的例子,這個時候就可以用互斥鎖來實現(xiàn)。

注意的坑

1. 不同 goroutine 可以 Unlock 同一個 Mutex,但是 Unlock 一個無鎖狀態(tài)的 Mutex 就會報錯。

2. 因為 mutex 沒有記錄 goroutine_id,所以要避免在不同的協(xié)程中分別進行上鎖/解鎖操作,不然很容易造成死鎖。

建議: 先 Lock 再 Unlock、兩者成對出現(xiàn)。

3. Mutex 不是可重入鎖

Mutex 不會記錄持有鎖的協(xié)程的信息,所以如果連續(xù)兩次 Lock 操作,就直接死鎖了。

如何實現(xiàn)可重入鎖?記錄上鎖的 goroutine 的唯一標識,在重入上鎖/解鎖的時候只需要增減計數(shù)。

type RecursiveMutex struct {
   sync.Mutex
   owner     int64 // 當前持有鎖的 goroutine id // 可以換成其他的唯一標識
   recursion int32 // 這個 goroutine 重入的次數(shù)
}
func (m *RecursiveMutex) Lock() {
   gid := goid.Get()  // 獲取唯一標識
   // 如果當前持有鎖的 goroutine 就是這次調用的 goroutine,說明是重入
   if atomic.LoadInt64(&m.owner) == gid {
      m.recursion++
      return
   }
   m.Mutex.Lock()
   // 獲得鎖的 goroutine 第一次調用,記錄下它的 goroutine id,調用次數(shù)加1
   atomic.StoreInt64(&m.owner, gid)
   m.recursion = 1
}
func (m *RecursiveMutex) Unlock() {
   gid := goid.Get()
   // 非持有鎖的 goroutine 嘗試釋放鎖,錯誤的使用
   if atomic.LoadInt64(&m.owner) != gid {
      panic(fmt.Sprintf("wrong the owner(%d): %d!", m.owner, gid))
   }
   // 調用次數(shù)減1
   m.recursion--
   if m.recursion != 0 { // 如果這個 goroutine 還沒有完全釋放,則直接返回
      return
   }
   // 此 goroutine 最后一次調用,需要釋放鎖
   atomic.StoreInt64(&m.owner, -1)
   m.Mutex.Unlock()
}

4.多高的 QPS 才能讓 Mutex 產生強烈的鎖競爭?

模擬一個 10ms 的接口,接口邏輯中使用全局共享的 Mutex,會發(fā)現(xiàn)在較低 QPS 的時候就開始產生激烈的鎖競爭(打印鎖等待時間和接口時間)。

解決方式:首先要盡量避免使用 Mutex。如果要使用 Mutex,盡量多聲明一些 Mutex,采用取模分片的方式去使用其中一個 Mutex 進行資源控制。避免一個 Mutex 對應過多的并發(fā)。

簡單總結:壓測或者流量高的時候發(fā)現(xiàn)系統(tǒng)不正常,打開 pprof 發(fā)現(xiàn) goroutine 指標在飆升,并且大量 Goroutine 都阻塞在 Mutex 的 Lock 上,這種現(xiàn)象下基本就可以確定是鎖競爭。

5. Mutex 千萬不能被復制

因為復制的時候會將原鎖的 state 值也進行復制。復制之后,一個新 Mutex 可能莫名處于持有鎖、喚醒或者饑餓狀態(tài),甚至等阻塞等待數(shù)量遠遠大于0。而原鎖 Unlock 的時候,卻不會影響復制鎖。

關于鎖的使用建議

寫業(yè)務時不能全局使用同一個 Mutex

千萬不要將要加鎖和解鎖分到兩個以上 Goroutine 中進行(容易形成死鎖)

Mutex 千萬不能被復制(包括不能通過函數(shù)參數(shù)傳遞),否則會復制傳參前鎖的狀態(tài):已鎖定 or 未鎖定。很容易產生死鎖,關鍵是編譯器還發(fā)現(xiàn)不了這個 Deadlock~

盡量避免使用 Mutex,如果非使用不可,盡量多聲明一些 Mutex,采用取模分片的方式去使用其中一個 Mutex(分段鎖)(盡量減小鎖的顆粒度)

參考

標準庫文檔 —— sync.Mutex

以上就是Golang Mutex錯過會后悔的重要知識點分享的詳細內容,更多關于Golang Mutex的資料請關注腳本之家其它相關文章!

相關文章

  • Gin框架中的PostForm用法及說明

    Gin框架中的PostForm用法及說明

    這篇文章主要介紹了Gin框架中的PostForm用法及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • Golang實現(xiàn)支付寶沙箱支付的方法步驟

    Golang實現(xiàn)支付寶沙箱支付的方法步驟

    本文主要介紹了Golang實現(xiàn)支付寶沙箱支付的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • GO常見的錯誤99%程序員會遇到(解決方法)

    GO常見的錯誤99%程序員會遇到(解決方法)

    這篇文章主要介紹了GO常見的錯誤99%程序員會遇到,本文給出了解決方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-01-01
  • go語言里包的用法實例

    go語言里包的用法實例

    這篇文章主要介紹了go語言里包的用法,實例分析了Go語言里包的原理與使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • golang gorm多條件篩選查詢操作

    golang gorm多條件篩選查詢操作

    這篇文章主要介紹了golang gorm多條件篩選查詢操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言學習教程之反射的示例詳解

    Go語言學習教程之反射的示例詳解

    這篇文章主要通過記錄對reflect包的簡單使用,來對反射有一定的了解。文中的示例代碼講解詳細,對我們學習Go語言有一定幫助,需要的可以參考一下
    2022-09-09
  • go語言yaml轉map、map遍歷的實現(xiàn)

    go語言yaml轉map、map遍歷的實現(xiàn)

    本文主要介紹了go語言yaml轉map、map遍歷的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Windows下CMD執(zhí)行Go出現(xiàn)中文亂碼的解決方法

    Windows下CMD執(zhí)行Go出現(xiàn)中文亂碼的解決方法

    本文主要介紹了Windows下CMD執(zhí)行Go出現(xiàn)中文亂碼的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-02-02
  • 詳解Opentelemetry Collector采集器

    詳解Opentelemetry Collector采集器

    這篇文章主要為大家介紹了Opentelemetry Collector神秘的采集器詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Go項目的目錄結構詳解

    Go項目的目錄結構詳解

    這篇文章主要介紹了Go項目的目錄結構,對基礎目錄做了講解,對項目開發(fā)中的其它目錄也一并做了介紹,需要的朋友可以參考下
    2014-10-10

最新評論