1. 程式人生 > >Golang 入門系列(十六)鎖的使用場景主要涉及到哪些?讀寫鎖為什麼會比普通鎖快

Golang 入門系列(十六)鎖的使用場景主要涉及到哪些?讀寫鎖為什麼會比普通鎖快

前面已經講過很多Golang系列知識,感興趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863.html,

接下來要說的是golang的鎖的使用場景主要涉及到哪些?讀寫鎖為什麼會比普通鎖快。

 

一、什麼場景下需要用到鎖

當程式中就一個執行緒的時候,是不需要加鎖的,但是通常實際的程式碼不會只是單執行緒,有可能是多個執行緒同時訪問公共資源,所以這個時候就需要用到鎖了,那麼關於鎖的使用場景主要涉及到哪些呢?

1. 多個執行緒在讀相同的資料時
2. 多個執行緒在寫相同的資料時
3. 同一個資源,有讀又有寫時

 

二、Go中鎖分為兩種:

  • 互斥鎖 (sync.Mutex)
  • 讀寫鎖 (sync.RWMutex 底層依賴Mutex實現  )

互斥鎖是併發程式對公共資源訪問限制最常見的方式。在Go中,sync.Mutex 提供了互斥鎖的實現。

當一個goroutine獲得了Mutex後,其他goroutine只能等待,除非該goroutine釋放這個Mutex。

互斥鎖結構:

type Mutex struct {
    state int32
    sema  uint32
}

1. 鎖定狀態值為1,未鎖定狀態鎖未0 。

2. Lock()加鎖、Unlock解鎖。

 

讀寫鎖則是對讀寫操作進行加鎖。需要注意的是多個讀操作之間不存在互斥關係,這樣提高了對共享資源的訪問效率。

Go中讀寫鎖由 sync.RWMutex 提供,RWMutex在讀鎖佔用的情況下,會阻止寫,但不阻止讀。RWMutex在寫鎖佔用情況下,會阻止任何其他goroutine(無論讀和寫)進來,整個鎖相當於由該goroutine獨佔。

讀寫鎖結構:

type RWMutex struct {
    w           Mutex  // held if there are pending writers
    writerSem   uint32 // semaphore for writers to wait for completing readers
    readerSem   uint32 // semaphore for readers to wait for completing writers
    readerCount int32  // number of pending readers
    readerWait  int32  // number of departing readers
}

1. RWMutex是單寫多讀鎖,該鎖可以加多個讀鎖或者一個寫鎖。

2. 讀鎖佔用的情況會阻止寫,不會阻止讀,多個goroutine可以同時獲取讀鎖。

3. 寫鎖會阻止其他gorotine不論讀或者寫進來,整個鎖由寫鎖goroutine佔用 與第一條共用示範程式碼

4. 適用於讀多寫少的場景


三、如何使用互斥鎖

Mutex為互斥鎖,Lock() 加鎖,Unlock() 解鎖,使用Lock() 加鎖後,便不能再次對其進行加鎖,直到利用Unlock()解鎖對其解鎖後,才能再次加鎖.適用於讀寫不確定場景,即讀寫次數沒有明顯的區別,並且只允許只有一個讀或者寫的場景,所以該鎖葉叫做全域性鎖。

互斥鎖只能鎖定一次,當在解鎖之前再次進行加鎖,便會無法加鎖。如果在加鎖前解鎖,便會報錯"panic: sync: unlock of unlocked mutex"。 

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 //最後的執行緒列印輸出

對於上面的程式,a作為一個公共的資源,所以對a的改變、讀寫等操作都需要加鎖。

 

需要注意的問題:

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

 

四、如何使用讀寫鎖

讀寫鎖的場景主要是在多執行緒的安全操作下,並且讀的情況多於寫的情況,也就是說既滿足多執行緒操作的安全性,也要確保效能不能太差,這時候,我們可以考慮使用讀寫鎖。當然你也可以簡單暴力直接使用互斥鎖(Mutex)。

Lock() 寫鎖,如果在新增寫鎖之前已經有其他的讀鎖和寫鎖,則lock就會阻塞直到該鎖可用,為確保該鎖最終可用,已阻塞的 Lock 呼叫會從獲得的鎖中排除新的讀取器,即寫鎖許可權高於讀鎖,有寫鎖時優先進行寫鎖定。

Unlock() 寫鎖解鎖,如果沒有進行寫鎖定,則就會引起一個執行時錯誤。

RLock() 讀鎖,當有寫鎖時,無法載入讀鎖,當只有讀鎖或者沒有鎖時,可以載入讀鎖,讀鎖可以載入多個,所以適用於"讀多寫少"的場景。

RUnlock() 讀鎖解鎖,RUnlock 撤銷單次RLock 呼叫,它對於其它同時存在的讀取器則沒有效果。若 rw 並沒有為讀取而鎖定,呼叫 RUnlock 就會引發一個執行時錯誤。

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 

看著挺複雜的,其實簡單來說就是:

  1. 讀鎖不能阻塞讀鎖

  2. 讀鎖需要阻塞寫鎖,直到所有讀鎖都釋放

  3. 寫鎖需要阻塞讀鎖,直到所有寫鎖都釋放

  4. 寫鎖需要阻塞寫鎖

 

五、最後

以上,就把golang中各種鎖的使用場景及怎麼使用互斥鎖和讀寫鎖等相關內容介紹完了,希望能對大家有所幫助。

&n