Go互斥鎖和讀寫鎖完整代碼舉例
作用
互斥鎖(Mutex)和讀寫鎖(RWMutex)是Go語言中用于保護(hù)共享資源、防止數(shù)據(jù)競(jìng)爭(zhēng)的重要同步機(jī)制。
1. 數(shù)據(jù)競(jìng)爭(zhēng)問題
1.1 數(shù)據(jù)競(jìng)爭(zhēng)
數(shù)據(jù)競(jìng)爭(zhēng)(Data Race)發(fā)生在多個(gè)goroutine并發(fā)訪問同一共享變量,且至少有一個(gè)訪問是寫入操作時(shí)。如果沒有適當(dāng)?shù)耐綑C(jī)制,會(huì)導(dǎo)致不可預(yù)測(cè)的結(jié)果。
1.2 數(shù)據(jù)競(jìng)爭(zhēng)的危害
- 結(jié)果不確定性:相同的代碼可能產(chǎn)生不同的結(jié)果
- 內(nèi)存損壞:可能導(dǎo)致程序崩潰或數(shù)據(jù)損壞
- 難以調(diào)試:?jiǎn)栴}可能只在特定條件下出現(xiàn)
2. 互斥鎖(Mutex)
2.1 互斥鎖
互斥鎖(Mutual Exclusion Lock)是最基本的同步原語,保證同一時(shí)間只有一個(gè)goroutine可以訪問受保護(hù)的臨界區(qū)。
2.2 互斥鎖操作
var mutex sync.Mutex // 加鎖 mutex.Lock() // 臨界區(qū)代碼 // ... // 解鎖 mutex.Unlock()
3. 代碼示例分析
3.1 數(shù)據(jù)競(jìng)爭(zhēng)演示
package test_lock
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wait sync.WaitGroup
var count1 = 0
func Demo01() {
wait.Add(10)
for i := 0; i < 10; i++ {
go func(data *int) {
// 模擬訪問耗時(shí)
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
// 訪問數(shù)據(jù)
temp := *data
// 模擬計(jì)算耗時(shí)
// 拉大時(shí)間,讓數(shù)據(jù)競(jìng)爭(zhēng)更嚴(yán)重(更好觀察結(jié)果)
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
ans := 1
// 修改數(shù)據(jù)
*data = temp + ans
fmt.Println("goroutine", i, "count1:", *data)
wait.Done()
}(&count1)
}
wait.Wait()
fmt.Println("最終結(jié)果", count1)
}
核心要點(diǎn):
無鎖并發(fā)訪問:
var count1 = 0 go func(data *int) { temp := *data // 讀取共享變量 // ... 計(jì)算耗時(shí) *data = temp + ans // 寫入共享變量 }(&count1)數(shù)據(jù)競(jìng)爭(zhēng)特征:
- 多個(gè)goroutine同時(shí)讀寫
count1變量 - 讀-改-寫操作不是原子的
- 最終結(jié)果通常小于預(yù)期值
- 多個(gè)goroutine同時(shí)讀寫
關(guān)鍵:
- 典型的數(shù)據(jù)競(jìng)爭(zhēng)
- 由于goroutine執(zhí)行順序不確定,結(jié)果不可預(yù)測(cè)
- 需要同步機(jī)制來保證正確性
3.2 互斥鎖解決方案
package test_lock
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
var count = 0
// 聲明一個(gè)互斥鎖
var mutex sync.Mutex
func Demo02() {
wg.Add(10)
for i := 0; i < 10; i++ {
go func(data *int) {
// 加鎖
mutex.Lock()
// 模擬訪問耗時(shí)
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
// 訪問數(shù)據(jù)
temp := *data
// 模擬計(jì)算耗時(shí)
// 拉大時(shí)間,讓數(shù)據(jù)競(jìng)爭(zhēng)更嚴(yán)重(更好觀察結(jié)果)
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
ans := 1
// 修改數(shù)據(jù)
*data = temp + ans
// 解鎖
mutex.Unlock()
fmt.Println("goroutine", i, "count:", *data)
wg.Done()
}(&count)
}
wg.Wait()
fmt.Println("最終結(jié)果", count)
}
核心要點(diǎn):
互斥鎖保護(hù)臨界區(qū):
var mutex sync.Mutex go func(data *int) { // 加鎖 mutex.Lock() temp := *data // ... 計(jì)算耗時(shí) *data = temp + ans // 解鎖 mutex.Unlock() }(&count)鎖的正確使用:
- 在訪問共享資源前加鎖
- 在操作完成后立即解鎖
- 使用
defer確保解鎖被執(zhí)行
關(guān)鍵:
- 互斥鎖保證了操作的原子性
- 最終結(jié)果總是正確的(10個(gè)goroutine,每個(gè)+1,結(jié)果應(yīng)為10)
- 性能會(huì)有一定損耗,但保證了正確性
3.3 讀寫鎖(RWMutex)
package test_lock
import (
"fmt"
"sync"
"time"
)
var wg3 sync.WaitGroup
var rw sync.RWMutex
// 實(shí)現(xiàn)可以多人讀 但是只能一人寫
func write(count int) {
defer wg3.Done()
rw.Lock()
defer rw.Unlock()
fmt.Println("goroutine", count, "寫操作>>>>>>>")
time.Sleep(time.Second * 2)
}
func read(count int) {
defer wg3.Done()
fmt.Println("goroutine", count, "<<<<<<<<<讀操作")
time.Sleep(time.Second * 2)
}
func Demo03() {
for i := 0; i < 10; i++ {
wg3.Add(1)
go write(i)
}
for i := 0; i < 10; i++ {
wg3.Add(1)
go read(i)
}
wg3.Wait()
}
核心要點(diǎn):
讀寫鎖操作:
var rw sync.RWMutex // 寫鎖操作 func write(count int) { rw.Lock() // 加寫鎖 defer rw.Unlock() // 解讀鎖 // 寫操作... } // 讀鎖操作 func read(count int) { rw.RLock() // 加讀鎖 defer rw.RUnlock() // 解讀鎖 // 讀操作... }讀寫鎖特性:
- 讀鎖:多個(gè)goroutine可以同時(shí)持有讀鎖
- 寫鎖:寫鎖是排他的,持有寫鎖時(shí)不能有讀鎖或其他寫鎖
- 升級(jí)規(guī)則:讀鎖不能升級(jí)為寫鎖
關(guān)鍵:
- 讀寫鎖適用于"讀多寫少"的場(chǎng)景
- 提高了并發(fā)讀取的性能
- 寫操作仍然需要獨(dú)占訪問
4. 鎖的類型對(duì)比
4.1 互斥鎖 vs 讀寫鎖
| 特性 | 互斥鎖(Mutex) | 讀寫鎖(RWMutex) |
|---|---|---|
| 并發(fā)讀取 | 不支持 | 支持多個(gè)goroutine同時(shí)讀取 |
| 寫入操作 | 完全互斥 | 完全互斥 |
| 性能開銷 | 較低 | 較高(需要維護(hù)讀鎖計(jì)數(shù)) |
| 適用場(chǎng)景 | 讀寫頻率相當(dāng) | 讀多寫少 |
4.2 選擇原則
使用互斥鎖的情況:
- 讀寫操作頻率相當(dāng)
- 臨界區(qū)代碼執(zhí)行時(shí)間短
- 代碼邏輯簡(jiǎn)單
使用讀寫鎖的情況:
- 讀操作遠(yuǎn)多于寫操作
- 讀操作耗時(shí)較長
- 需要最大化讀取并發(fā)性
5. 鎖的使用場(chǎng)景
5.1 共享數(shù)據(jù)保護(hù)
// 保護(hù)共享map
var dataMap = make(map[string]string)
var mapMutex sync.RWMutex
func GetValue(key string) string {
mapMutex.RLock()
defer mapMutex.RUnlock()
return dataMap[key]
}
func SetValue(key, value string) {
mapMutex.Lock()
defer mapMutex.Unlock()
dataMap[key] = value
}
5.2 計(jì)數(shù)器保護(hù)
// 原子計(jì)數(shù)器
var counter int
var counterMutex sync.Mutex
func Increment() {
counterMutex.Lock()
defer counterMutex.Unlock()
counter++
}
func GetCount() int {
counterMutex.Lock()
defer counterMutex.Unlock()
return counter
}
5.3 資源池管理
// 連接池管理
type ConnectionPool struct {
pool []*Connection
mutex sync.Mutex
}
func (p *ConnectionPool) Get() *Connection {
p.mutex.Lock()
defer p.mutex.Unlock()
if len(p.pool) > 0 {
conn := p.pool[0]
p.pool = p.pool[1:]
return conn
}
return nil
}
6. 指南
6.1 鎖的使用
保持鎖的粒度適中:
// 錯(cuò)誤:鎖的粒度過大 mutex.Lock() // 大量非臨界區(qū)代碼... // 共享資源訪問 mutex.Unlock() // 正確:只保護(hù)必要的臨界區(qū) // 非臨界區(qū)代碼... mutex.Lock() // 共享資源訪問 mutex.Unlock() // 非臨界區(qū)代碼...
使用defer確保解鎖:
func safeOperation() { mutex.Lock() defer mutex.Unlock() // 確保在任何情況下都會(huì)解鎖 // 可能panic的代碼 if err != nil { panic("error") } }
6.2 避免死鎖
鎖的順序一致性:
// 錯(cuò)誤:可能產(chǎn)生死鎖 func operation1() { mutexA.Lock() mutexB.Lock() // ... mutexB.Unlock() mutexA.Unlock() } func operation2() { mutexB.Lock() mutexA.Lock() // 可能死鎖 // ... } // 正確:保持一致的鎖順序 func operation1() { mutexA.Lock() mutexB.Lock() // ... } func operation2() { mutexA.Lock() // 先獲取A鎖 mutexB.Lock() // 再獲取B鎖 // ... }使用超時(shí)機(jī)制:
// 使用context實(shí)現(xiàn)超時(shí) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() select { case <-acquireLock(ctx, mutex): // 成功獲取鎖 case <-ctx.Done(): // 超時(shí)處理 }
6.3 性能優(yōu)化
減少鎖競(jìng)爭(zhēng):
- 使用更細(xì)粒度的鎖
- 考慮使用無鎖數(shù)據(jù)結(jié)構(gòu)
- 使用本地緩存減少鎖的使用頻率
讀寫分離:
// 使用讀寫鎖優(yōu)化讀多寫少的場(chǎng)景 var rw sync.RWMutex var data []string // 多個(gè)goroutine可以同時(shí)讀取 func ReadData() []string { rw.RLock() defer rw.RUnlock() return data } // 寫操作仍然需要互斥 func WriteData(newData []string) { rw.Lock() defer rw.Unlock() data = newData }
7. 高級(jí)主題
7.1 條件變量(Cond)
條件變量用于在特定條件下等待或通知goroutine:
var mutex sync.Mutex
cond := sync.NewCond(&mutex)
// 等待條件
func waitForCondition() {
mutex.Lock()
for !condition {
cond.Wait() // 釋放鎖并等待
}
// 條件滿足,執(zhí)行操作
mutex.Unlock()
}
// 通知條件滿足
func signalCondition() {
mutex.Lock()
condition = true
cond.Signal() // 通知一個(gè)等待的goroutine
mutex.Unlock()
}
7.2 原子操作
對(duì)于簡(jiǎn)單的計(jì)數(shù)器操作,可以使用原子操作避免鎖的開銷:
import "sync/atomic"
var counter int64
func Increment() {
atomic.AddInt64(&counter, 1)
}
func GetCount() int64 {
return atomic.LoadInt64(&counter)
}
8. 調(diào)試和測(cè)試
8.1 數(shù)據(jù)競(jìng)爭(zhēng)檢測(cè)
使用Go內(nèi)置的競(jìng)爭(zhēng)檢測(cè)器:
# 編譯時(shí)啟用競(jìng)爭(zhēng)檢測(cè) go build -race main.go # 運(yùn)行程序 go run -race main.go
8.2 性能分析
使用pprof分析鎖競(jìng)爭(zhēng):
import _ "net/http/pprof"
// 在程序中啟動(dòng)pprof服務(wù)器
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
總結(jié)
到此這篇關(guān)于Go互斥鎖和讀寫鎖的文章就介紹到這了,更多相關(guān)Go互斥鎖和讀寫鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GoLang之標(biāo)準(zhǔn)庫encoding/json包
本文主要介紹了GoLang之標(biāo)準(zhǔn)庫encoding/json包,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Go+Kafka實(shí)現(xiàn)延遲消息的實(shí)現(xiàn)示例
本文主要介紹了Go+Kafka實(shí)現(xiàn)延遲消息的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Go應(yīng)用中優(yōu)雅處理Error的技巧總結(jié)
在程序員中,尤其是go新手,經(jīng)常聽到的一個(gè)討論話題是:如何處理錯(cuò)誤,這篇文章主要給大家介紹了關(guān)于Go應(yīng)用中優(yōu)雅處理Error的一些相關(guān)技巧,需要的朋友可以參考下2021-09-09
詳解Go多協(xié)程并發(fā)環(huán)境下的錯(cuò)誤處理
這篇文章主要介紹了詳解Go多協(xié)程并發(fā)環(huán)境下的錯(cuò)誤處理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

