天天看點

Golang 并發和鎖

Golang 并發和鎖

1.介紹

讀寫鎖介紹

基本遵循兩大原則:

1、可以随便讀,多個goroutine同時讀

2、寫的時候,啥也不能幹。不能讀也不能寫

RWMutex提供了四個方法:

func (*RWMutex) Lock // 寫鎖定

func (*RWMutex) Unlock // 寫解鎖

func (*RWMutex) RLock // 讀鎖定

func (*RWMutex) RUnlock // 讀解鎖

  • 讀鎖的時候無需等待讀鎖的結束
  • 讀鎖的時候要等待寫鎖的結束
  • 寫鎖的時候要等待讀鎖的結束
  • 寫鎖的時候要等待寫鎖的結束

其他介紹

golang中的鎖分為互斥鎖、讀寫鎖、原子鎖即原子操作。在 Golang 裡有專門的方法來實作鎖,就是 sync 包,這個包有兩個很重要的鎖類型。一個叫 Mutex, 利用它可以實作互斥鎖。一個叫 RWMutex,利用它可以實作讀寫鎖。

全局鎖 sync.Mutex,是同一時刻某一資源隻能上一個鎖,此鎖具有排他性,上鎖後隻能被此線程使用,直至解鎖。加鎖後即不能讀也不能寫。全局鎖是互斥鎖,即 sync.Mutex 是個互斥鎖。

讀寫鎖 sync.RWMutex ,将使用者分為讀者和寫者兩個概念,支援同時多個讀者一起讀共享資源,但寫時隻能有一個,并且在寫時不可以讀。理論上來說,sync.RWMutex 的 Lock() 也是個互斥鎖。

将上面的結論展開一下,更清晰得說(為避免了解偏差甯可唠叨一些):

sync.Mutex 的鎖是不可以嵌套使用的。

sync.RWMutex 的 mu.Lock() 是不可以嵌套的。

sync.RWMutex 的 mu.Lock() 中不可以嵌套 mu.RLock()。(這是個注意的地方)

否則,會 panic fatal error: all goroutines are asleep - deadlock!

下面三種均會死鎖

func lockAndRead1() { // 全局鎖内使用全局鎖
  l.Lock()
  defer l.Unlock()
 
  l.Lock()
  defer l.Unlock()
}
 
func lockAndRead2() { // 全局鎖内使用可讀鎖
  l.Lock()
  defer l.Unlock() // 由于 defer 是棧式執行,是以這兩個鎖是嵌套結構
 
  l.RLock()
  defer l.RUnlock()
}
 
func lockAndRead3() { // 可讀鎖内使用全局鎖
  l.RLock()
  defer l.RUnlock()
 
  l.Lock()
  defer l.Unlock()
}      
var l sync.RWMutex
 
func lockAndRead() { // 可讀鎖内使用可讀鎖
  l.RLock()
  defer l.RUnlock()
 
  l.RLock()
  defer l.RUnlock()
}
 
func main() {
  lockAndRead()
  time.Sleep(5 * time.Second)
}      

上面這種方式不會嵌套。

2.讀寫鎖執行個體

2.1 同時讀

package main

import (
    "sync"
    "time"
)

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)
    
    // 多個同時讀
    go read(1)
    go read(2)

    time.Sleep(2*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}      

結果

1 read start

1 reading 

2 read start

2 reading

1 read over

2 read over      

2.2 讀寫交替

package main

import (
    "sync"
    "time"
)

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)
    
    // 寫的時候啥也不能幹
    go write(1)
    go read(2)
    go write(3)

    time.Sleep(2*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}

func write(i int) {
    println(i,"write start")

    m.Lock()
    println(i,"writing")
    time.Sleep(1*time.Second)
    m.Unlock()

    println(i,"write over")
}      

結果

1 write start

1 writing

2 read start

3 write start

1 writing over

2 reading

2 read over

3 writing

3 write over      

3.解決并發中goroutine未綁定變量

package main

import (
"fmt"
"time"
)
func main() {
names := []string{"niko","mike","tony"}
for _,name := range names{
go func (na string) {
fmt.Printf("%s\n",na)
}(name)
}
time.Sleep(time.Second * 2)
}      

​​傳送門1​​

​​傳送門2​​

4.互斥鎖

互斥鎖有兩個方法:加鎖、解鎖。

一個互斥鎖隻能同時被一個 goroutine 鎖定,其它 goroutine 将阻塞直到互斥鎖被解鎖(重新争搶對互斥鎖的鎖定)。使用Lock加鎖後,不能再進行加鎖,隻有當對其進行Unlock解鎖之後,才能對其加鎖。這個很好了解。

如果對一個未加鎖的資源進行解鎖,會引發panic異常。

可以在一個goroutine中對一個資源加鎖,而在另外一個goroutine中對該資源進行解鎖。

不要在持有鎖的時候做 IO 操作。盡量隻通過持有鎖來保護 IO 操作需要的資源而不是 IO 操作本身。

func (m *Mutex) Lock()
func (m *Mutex) Unlock()      

5.鎖拷貝問題

type MyMutex struct {
  count int
  sync.Mutex
}
 
func main() {
  var mu MyMutex
  mu.Lock()
  var mu1 = mu
  mu.count++
  mu.Unlock()
  mu1.Lock() //已經加鎖,此時再加鎖會死鎖
  mu1.count++
  mu1.Unlock()
  fmt.Println(mu.count, mu1.count)
}      

加鎖後複制變量,會将鎖的狀态也複制,是以 mu1 其實是已經加鎖狀态,再加鎖會死鎖

6.參考文章

繼續閱讀