網頁

2022/12/17

Golang RWMutex 讀寫互斥鎖簡介

Go goroutine做並行程序時,利用sync.RWMutex讀寫鎖(read write lock)限制goroutine對競爭資源的讀取及寫入。


RWMutex提供用來鎖定goroutine共享資源的讀鎖(read lock)和寫鎖(write lock)。

RWMutexMutex的差別為RWMutex對共用資源的讀取和寫入提供讀鎖與寫鎖,讀鎖間不排斥、讀寫鎖及寫讀鎖相互排斥;而Mutex只有一種鎖,不分讀寫互相排斥。


讀鎖 RWMutex.RLock

RWMutex.RLock為讀鎖;RWMutex.RUnlock為解鎖讀鎖。讀鎖允許同時由多個goroutine取得,即共用資源可同時被多個goroutine取得讀鎖,不過共用資源上讀鎖時其他goroutine無法取得寫鎖;反之亦然。


寫鎖 RWMutex.Lock

RWMutex.Lock為寫鎖;RWMutex.Unlock為解鎖寫鎖。寫鎖僅允許同時被一個goroutine取得,共用資源上寫鎖時其他goroutine無法取得讀鎖;反之亦然。


範例環境:

  • Go 1.19


未加鎖

下面count為被多個goroutine存取的共用資源;add()累加count的值;print()印出count的值。add()print()存取count時未加任何鎖。add()print()由不同的goroutine執行。

main.go

package main

import (
    "fmt"
    "sync"
    "time"
)

var count int = 0
var rwmu sync.RWMutex

func main() {
    go add()
    go print()
    go print()

    time.Sleep(time.Second * 1)
}

func print() {
    // rwmu.RLock()
    // defer rwmu.RUnlock()
    fmt.Printf("count=%d\n", count)
}

func add() {
    // rwmu.Lock()
    // defer rwmu.Unlock()

    for i := 0; i < 100000; i++ {
        count++
    }
    fmt.Println("add finished!")
}

執行結果可能如下,在未上鎖的情況下count的值在goroutine add()累加結束前的過程中會被其他goroutine print()印出。

count=4013
count=12156
add finished!


僅加RWMutex.Lock寫鎖

在goroutine add()累加(寫入)count時加上寫鎖RWMutex.Lock,即無法被其他goroutine取得讀鎖或寫鎖。然而goroutine print()取得count未加任何鎖所以效果同上,即count的值在goroutine add()累加結束前的過程中仍會被goroutine print()取得並印出。

main.go

package main

import (
    "fmt"
    "sync"
    "time"
)

var count int = 0
var rwmu sync.RWMutex

func main() {
    go add()
    go print()
    go print()

    time.Sleep(time.Second * 1)
}

func print() {
    // rwmu.RLock()
    // defer rwmu.RUnlock()
    fmt.Printf("count=%d\n", count)
}

func add() {
    rwmu.Lock()
    defer rwmu.Unlock()

    for i := 0; i < 100000; i++ {
        count++
    }
    fmt.Println("add finished!")
}

執行結果可能如下。

count=4743
count=2838
add finished!


僅加RWMutex.RLock讀鎖

結果同上,由於goroutine add()寫入count時未加寫鎖因此不影響goroutine print()的讀鎖存取。

main.go

package main

import (
    "fmt"
    "sync"
    "time"
)

var count int = 0
var rwmu sync.RWMutex

func main() {
    go add()
    go print()
    go print()

    time.Sleep(time.Second * 1)
}

func print() {
    rwmu.RLock()
    defer rwmu.RUnlock()
    fmt.Printf("count=%d\n", count)
}

func add() {
    // rwmu.Lock()
    // defer rwmu.Unlock()

    for i := 0; i < 100000; i++ {
        count++
    }
    fmt.Println("add finished!")
}

執行結果可能如下。

count=5498
count=14995
add finished!


加RWMutex.RLock讀鎖及RWMutex.Lock寫鎖

在goroutine add()寫入count時加上寫鎖RWMutex.Lock,即無法被其他讀鎖及寫鎖存取。goroutine print()取得count時加上讀鎖RWMutex.RLock

main.go

package main

import (
    "fmt"
    "sync"
    "time"
)

var count int = 0
var rwmu sync.RWMutex

func main() {
    go add()
    go print()
    go print()

    time.Sleep(time.Second * 1)
}

func print() {
    rwmu.RLock()
    defer rwmu.RUnlock()
    fmt.Printf("count=%d\n", count)
}

func add() {
    rwmu.Lock()
    defer rwmu.Unlock()

    for i := 0; i < 100000; i++ {
        count++
    }
    fmt.Println("add finished!")
}

執行結果可能有以下三種。

兩個goroutine print()取得讀鎖前先等goroutineadd()的寫鎖釋放。

add finished!
count=100000
count=100000

一個goroutine print()先取得讀鎖印出後釋放,然後由goroutine add()取得寫鎖累加count值後釋放、最後由另一個goroutine print()取得讀鎖印出。

count=0
add finished!
count=100000

兩個goroutine print()先取得讀鎖印出後釋放,最後才由goroutine add()取得寫鎖累加count值。

count=0
count=0
add finished!

沒有留言:

張貼留言