首頁 > 軟體

golang並行安全及讀寫互斥鎖的範例分析

2022-04-14 16:02:08

並行安全和鎖

有時候在Go程式碼中可能會存在多個goroutine同時操作一個資源(臨界區),這種情況會發生競態問題(資料競態)。類比現實生活中的例子有十字路口被各個方向的的汽車競爭;還有火車上的衛生間被車廂裡的人競爭。

舉個例子:

var x int64
var wg sync.WaitGroup
func add() {
    for i := 0; i < 5000; i++ {
        x = x + 1
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
} 

上面的程式碼中我們開啟了兩個goroutine去累加變數x的值,這兩個goroutine在存取和修改x變數的時候就會存在資料競爭,導致最後的結果與期待的不符。

互斥鎖

import (
	"fmt"
	"sync"
)
var lock sync.Mutex
lock.Lock() // 加鎖
lock.Unlock() // 解鎖

互斥鎖是一種常用的控制共用資源存取的方法,它能夠保證同時只有一個goroutine可以存取共用資源。Go語言中使用sync包的Mutex型別來實現互斥鎖。 使用互斥鎖來修復上面程式碼的問題:

var x int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
    for i := 0; i < 5000; i++ {
        lock.Lock() // 加鎖
        x = x + 1
        lock.Unlock() // 解鎖
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
} 

使用互斥鎖能夠保證同一時間有且只有一個goroutine進入臨界區,其他的goroutine則在等待鎖;當互斥鎖釋放後,等待的goroutine才可以獲取鎖進入臨界區,多個goroutine同時等待一個鎖時,喚醒的策略是隨機的。

讀寫互斥鎖

import (
	"fmt"
	"sync"
)
var rwlock sync.RWMutex
rwlock.Lock()   // 加寫鎖
rwlock.Unlock() // 解寫鎖

互斥鎖是完全互斥的,但是有很多實際的場景下是讀多寫少的,當我們並行的去讀取一個資源不涉及資源修改的時候是沒有必要加鎖的,這種場景下使用讀寫鎖是更好的一種選擇。讀寫鎖在Go語言中使用sync包中的RWMutex型別。

讀寫鎖分為兩種:讀鎖和寫鎖。當一個goroutine獲取讀鎖之後,其他的goroutine如果是獲取讀鎖會繼續獲得鎖,如果是獲取寫鎖就會等待;當一個goroutine獲取寫鎖之後,其他的goroutine無論是獲取讀鎖還是寫鎖都會等待。

讀寫鎖範例:

var (
    x      int64
    wg     sync.WaitGroup
    lock   sync.Mutex
    rwlock sync.RWMutex
)
func write() {
    // lock.Lock()   // 加互斥鎖
    rwlock.Lock() // 加寫鎖
    x = x + 1
    time.Sleep(10 * time.Millisecond) // 假設讀操作耗時10毫秒
    rwlock.Unlock()                   // 解寫鎖
    // lock.Unlock()                     // 解互斥鎖
    wg.Done()
}
func read() {
    // lock.Lock()                  // 加互斥鎖
    rwlock.RLock()               // 加讀鎖
    time.Sleep(time.Millisecond) // 假設讀操作耗時1毫秒
    rwlock.RUnlock()             // 解讀鎖
    // lock.Unlock()                // 解互斥鎖
    wg.Done()
}
func main() {
    start := time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }
    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
}

需要注意的是讀寫鎖非常適合讀多寫少的場景,如果讀和寫的操作差別不大,讀寫鎖的優勢就發揮不出來。

以上就是golang並行安全及讀寫互斥鎖的範例分析的詳細內容,更多關於golang並行安全及讀寫互斥鎖的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com