RWMutex簡介
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高併發性,因爲在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數爲實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。
應用場景
讀寫鎖多用於讀多寫少的場景
- 比如在配置中,讀取一次配置後,很少修改配置;但存在修改配置的情況。
- 在一些初始化的結構體,或者數組中,很少動用修改,大多數都是讀取操作。
問題
這裏有一個前提條件:在大併發讀的時候纔會發生。
問:如果有10000 reader, 一個writer,writer要等多久才能實際發生write操作?
答: 寫,需要等到沒有讀鎖才能操作。
測試代碼如下:
package main
import (
"sync"
"fmt"
"time"
)
func main() {
rwMux := new(sync.RWMutex)
names := make(map[int]string)
for i := 0; i < 10; i++ {
names[i] = fmt.Sprintf("name %d",i)
}
go func() {
for i := 0; i < 100000; i++ {
go func(i int) {
rwMux.RLock()
fmt.Println(names[1],i)
rwMux.RUnlock()
}(i)
}
}()
time.Sleep(time.Millisecond)
go func() {
for i := 0; i < 10; i++ {
go func(i int) {
rwMux.Lock()
names[i] = fmt.Sprintf("hello %d",i)
fmt.Println("writer....")
rwMux.Unlock()
}(i)
}
}()
time.Sleep(time.Second * 10)
}
所以說:寫的延遲完全沒有保障。
解決方案
方案當然不是我這菜鳥想的,是羣裏一個大佬隨便提了一句。
原話:
rwlock這種我覺得還不如用lock,然後定時copy出來一份給read,這樣他的資源 競爭被bound在thread個數上,好得多
副本的訪問更新用atomic操作讀寫就行了,這樣競爭被bound在cpu core個數上
atomic.LoadPointer來做object的更新
在此我的理解的是:
在寫的時候加互斥鎖,並返回一個副本,在讀的時候不加鎖,都去讀取整個副本。
後面又去查了一些資料,有興趣的可以去了解一下。
官方已經有大佬的思路實現
我這裏也貼出代碼:
package main
import (
"sync"
"sync/atomic"
)
func main() {
type Map map[string]string
var m atomic.Value
m.Store(make(Map))
var mu sync.Mutex // used only by writers
// read function can be used to read the data without further synchronization
read := func(key string) (val string) {
m1 := m.Load().(Map)
return m1[key]
}
// insert function can be used to update the data without further synchronization
insert := func(key, val string) {
mu.Lock() // synchronize with other potential writers
defer mu.Unlock()
m1 := m.Load().(Map) // load current value of the data structure
m2 := make(Map) // create a new value
for k, v := range m1 {
m2[k] = v // copy all data from the current object to the new one
}
m2[key] = val // do the update that we need
m.Store(m2) // atomically replace the current object with the new one
// At this point all new readers start working with the new version.
// The old version will be garbage collected once the existing readers
// (if any) are done with it.
}
_, _ = read, insert
}
這就是官方的實現,思路都是一樣的。
寫在最後:
我寫這個文章主要是爲了說明,用一個技術的時候,不要僅僅停留在用的層面;同樣是大量讀,少量寫,爲什麼大併發來讀會有這些問題,你用的時候是否已經想到;所以多瞭解一下,多想一下爲什麼。