溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Golang中的Mutex怎么使用

發(fā)布時間:2023-05-10 17:25:08 來源:億速云 閱讀:122 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“Golang中的Mutex怎么使用”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

    Mutex

    基本概念

    MutexGo 語言中互斥鎖的實(shí)現(xiàn),它是一種同步機(jī)制,用于控制多個 goroutine 之間的并發(fā)訪問。當(dāng)多個 goroutine 嘗試同時訪問同一個共享資源時,可能會導(dǎo)致數(shù)據(jù)競爭和其他并發(fā)問題,因此需要使用互斥鎖來協(xié)調(diào)它們之間的訪問。

    Golang中的Mutex怎么使用

    在上述圖片中,我們可以將綠色部分看作是臨界區(qū)。當(dāng) g1 協(xié)程通過 mutex 對臨界區(qū)進(jìn)行加鎖后,臨界區(qū)將會被鎖定。此時如果 g2 想要訪問臨界區(qū),就會失敗并進(jìn)入阻塞狀態(tài),直到鎖被釋放,g2 才能拿到臨界區(qū)的訪問權(quán)。

    結(jié)構(gòu)體介紹

    type Mutex struct {
        state int32
        sema  uint32
    }

    字段:

    state

    state 是一個 int32 類型的變量,它存儲著 Mutex 的各種狀態(tài)信息(未加鎖、被加鎖、喚醒狀態(tài)、饑餓狀態(tài)),不同狀態(tài)通過位運(yùn)算進(jìn)行計(jì)算。

    sema

    sema 是一個信號量,用于實(shí)現(xiàn) Mutex 的等待和喚醒機(jī)制。

    方法:

    Lock()

    Lock() 方法用于獲取 Mutex 的鎖,如果 Mutex 已經(jīng)被其他的 goroutine 鎖定,則 Lock() 方法會一直阻塞,直到該 goroutine 獲取到鎖為止。

    UnLock()

    Unlock() 方法用于釋放 Mutex 的鎖,將 Mutex 的狀態(tài)設(shè)置為未鎖定的狀態(tài)。

    TryLock()

    Go 1.18 版本以后,sync.Mutex 新增一個 TryLock() 方法,該方法為非阻塞式的加鎖操作,如果加鎖成功,返回 true,否則返回 false。

    雖然 TryLock() 的用法確實(shí)存在,但由于其使用場景相對較少,因此在使用時應(yīng)該格外謹(jǐn)慎。TryLock() 方法注釋如下所示:

    // Note that while correct uses of TryLock do exist, they are rare,
    // and use of TryLock is often a sign of a deeper problem
    // in a particular use of mutexes.

    代碼示例

    我們先來看一個有并發(fā)安全問題的例子

    package main
    
    import (
       "fmt"
       "sync"
    )
    
    var cnt int
    
    func main() {
       var wg sync.WaitGroup
       for i := 0; i < 10; i++ {
          wg.Add(1)
          go func() {
             defer wg.Done()
             for j := 0; j < 10000; j++ {
                cnt++
             }
          }()
       }
       wg.Wait()
       fmt.Println(cnt)
    }

    在這個例子中,預(yù)期的 cnt 結(jié)果為 10 * 10000 = 100000。但是由于多個 goroutine 并發(fā)訪問了共享變量 cnt,并且沒有進(jìn)行任何同步操作,可能導(dǎo)致讀寫沖突(race condition),從而影響 cnt 的值和輸出結(jié)果的正確性。這種情況下,不能確定最終輸出的 cnt 值是多少,每次執(zhí)行程序得到的結(jié)果可能不同。

    在這種情況下,可以使用互斥鎖(sync.Mutex)來保護(hù)共享變量的訪問,保證只有一個 goroutine 能夠同時訪問 cnt,從而避免競態(tài)條件的問題。修改后的代碼如下:

    package main
    
    import (
       "fmt"
       "sync"
    )
    
    var cnt int
    var mu sync.Mutex
    
    func main() {
       var wg sync.WaitGroup
       for i := 0; i < 10; i++ {
          wg.Add(1)
          go func() {
             defer wg.Done()
             for j := 0; j < 10000; j++ {
                mu.Lock()
                cnt++
                mu.Unlock()
             }
          }()
       }
       wg.Wait()
       fmt.Println(cnt)
    }

    在這個修改后的版本中,使用互斥鎖來保護(hù)共享變量 cnt 的訪問,可以避免出現(xiàn)競態(tài)條件的問題。具體而言,在 cnt++ 操作前,先執(zhí)行 Lock() 方法,以確保當(dāng)前 goroutine 獲取到了互斥鎖并且獨(dú)占了共享變量的訪問權(quán)。在 cnt++ 操作完成后,再執(zhí)行 Unlock() 方法來釋放互斥鎖,從而允許其他 goroutine 獲取互斥鎖并訪問共享變量。這樣,只有一個 goroutine 能夠同時訪問 cnt,從而確保了最終輸出結(jié)果的正確性。

    易錯場景

    忘記解鎖

    如果使用 Lock() 方法之后,沒有調(diào)用 Unlock() 解鎖,會導(dǎo)致其他 goroutine 被永久阻塞。例如:

    package main
    
    import (
       "fmt"
       "sync"
       "time"
    )
    
    var mu sync.Mutex
    var cnt int
    
    func main() {
       go increase(1)
       go increase(2)
    
       time.Sleep(time.Second)
       fmt.Println(cnt)
    }
    
    func increase(delta int) {
       mu.Lock()
       cnt += delta
    }

    在上述代碼中,通常情況下,cnt 的結(jié)果應(yīng)該為 3。然而沒有解鎖操作,其中一個 goroutine 被阻塞,導(dǎo)致沒有達(dá)到預(yù)期效果,最終輸出的 cnt 可能只能為 12

    正確的做法是使用 defer 語句在函數(shù)返回前釋放鎖。

    func increase(delta int) {
       mu.Lock()
       defer mu.Unlock() // 通過 defer 語句在函數(shù)返回前釋放鎖
       cnt += delta
    }

    重復(fù)加鎖

    重復(fù)加鎖操作被稱為可重入操作。不同于其他一些編程語言的鎖實(shí)現(xiàn)(例如 JavaReentrantLock),Gomutex 并不支持可重入操作,如果發(fā)生了重復(fù)加鎖操作,就會導(dǎo)致死鎖。例如:

    package main
    
    import (
       "fmt"
       "sync"
       "time"
    )
    
    var mu sync.Mutex
    var cnt int
    
    func main() {
       go increase(1)
       go increase(2)
    
       time.Sleep(time.Second)
       fmt.Println(cnt)
    }
    
    func increase(delta int) {
       mu.Lock()
       mu.Lock()
       cnt += delta
       mu.Unlock()
    }

    在這個例子中,如果在 increase 函數(shù)中重復(fù)加鎖,將會導(dǎo)致 mu 鎖被第二次鎖住,而其他 goroutine 將被永久阻塞,從而導(dǎo)致程序死鎖。正確的做法是只對需要加鎖的代碼段進(jìn)行加鎖,避免重復(fù)加鎖。

    基于 Mutex 實(shí)現(xiàn)一個簡單的線程安全的緩存

    import "sync"
    
    type Cache struct {
       data map[string]any
       mu   sync.Mutex
    }
    
    func (c *Cache) Get(key string) (any, bool) {
       c.mu.Lock()
       defer c.mu.Unlock()
       value, ok := c.data[key]
       return value, ok
    }
    
    func (c *Cache) Set(key string, value any) {
       c.mu.Lock()
       defer c.mu.Unlock()
       c.data[key] = value
    }

    上述代碼實(shí)現(xiàn)了一個簡單的線程安全的緩存。使用 Mutex 可以保證同一時刻只有一個 goroutine 進(jìn)行讀寫操作,避免多個 goroutine 并發(fā)讀寫同一數(shù)據(jù)時產(chǎn)生數(shù)據(jù)不一致性的問題。

    對于緩存場景,讀操作比寫操作更頻繁,因此使用 RWMutex 代替 Mutex 會更好,因?yàn)?RWMutex 允許多個 goroutine 同時進(jìn)行讀操作,只有在寫操作時才會進(jìn)行互斥鎖定,從而減少了鎖的競爭,提高了程序的并發(fā)性能。

    “Golang中的Mutex怎么使用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI