您好,登錄后才能下訂單哦!
前面的有篇文章在講資源競爭的時候,提到了互斥鎖。互斥鎖的根本就是當一個goroutine訪問的時候,其他goroutine都不能訪問,這樣肯定保證了資源的同步,避免了競爭,不過也降低了性能。
仔細剖析我們的場景,當我們讀取一個數據的時候,如果這個數據永遠不會被修改,那么其實是不存在資源競爭的問題的。因為數據是不變的,不管怎么讀取,多少goroutine同時讀取,都是可以的。
所以其實讀取并不是問題,問題主要是修改。修改的數據要同步,這樣其他goroutine才可以感知到。所以真正的互斥應該是讀取和修改、修改和修改之間,讀取和讀取是沒有互斥操作的。
所以這就延伸出來另外一種鎖,叫做讀寫鎖。
讀寫鎖可以讓多個讀操作同時并發,同時讀取,但是對于寫操作是完全互斥的。也就是說,當一個goroutine進行寫操作的時候,其他goroutine既不能進行讀操作,也不能進行寫操作。
var count int
var wg sync.WaitGroup
func main() {
wg.Add(10)
for i:=0;i<5;i++ {
go read(i)
}
for i:=0;i<5;i++ {
go write(i);
}
wg.Wait()}func read(n int) {
fmt.Printf("讀goroutine %d 正在讀取...\n",n)
v := count
fmt.Printf("讀goroutine %d 讀取結束,值為:%d\n", n,v)
wg.Done()
}
func write(n int) {
fmt.Printf("寫goroutine %d 正在寫入...\n",n)
v := rand.Intn(1000)
count = v
fmt.Printf("寫goroutine %d 寫入結束,新值為:%d\n", n,v)
wg.Done()
}
以上我們定義了一個共享的資源count,并且聲明了兩個函數read和write進行讀寫。在main函數的測試中,我們同時啟動了 5 個讀寫goroutine進行讀寫操作,通過打印的結果來看,寫入操作是處于競爭狀態的,有的寫入操作被覆蓋了。通過go build -race也可以看到更明細的競爭態。
針對這種情況,第一個方案是加互斥鎖,同時只能有一個goroutine可以操作count。但是這種方法性能比較慢,而且我們說的讀操作可以不互斥,所以這種情況比較適合使用讀寫鎖。
var count int
var wg sync.WaitGroup
var rw sync.RWMutex
func main() {
wg.Add(10)
for i:=0;i<5;i++ {
go read(i)
}
for i:=0;i<5;i++ {
go write(i);
}
wg.Wait()}func read(n int) {
rw.RLock()
fmt.Printf("讀goroutine %d 正在讀取...\n",n)
v := count
fmt.Printf("讀goroutine %d 讀取結束,值為:%d\n", n,v)
wg.Done()
rw.RUnlock()}func write(n int) {
rw.Lock()
fmt.Printf("寫goroutine %d 正在寫入...\n",n)
v := rand.Intn(1000)
count = v
fmt.Printf("寫goroutine %d 寫入結束,新值為:%d\n", n,v)
wg.Done()
rw.Unlock()
}
我們在read里使用讀鎖,也就是RLock和RUnlock,寫鎖的方法名和我們平時使用的一樣,是Lock和Unlock。這樣,我們就使用了讀寫鎖,可以并發地讀,但是同時只能有一個寫,并且寫的時候不能進行讀操作。現在我們再運行代碼,可以從輸出的數據看到,可以讀到新值了。
我們同時也可以使用go build -race檢測,也沒有競爭提示了。
我們在做Java開發的時候,肯定知道SynchronizedMap這個Map,它是一個在多線程下安全的Map,我們可以通過Collections.synchronizedMap(Map<K, V>)來獲取一個安全的Map。下面我們看看如何使用讀寫鎖,基于Go語言來實現一個安全的Map。
package common
import (
"sync")
//安全的Map
type SynchronizedMap struct {
rw *sync.RWMutex
data map[interface{}]interface{}
}
//存儲操作
func (sm *SynchronizedMap) Put(k,v interface{}){
sm.rw.Lock()
defer sm.rw.Unlock()
sm.data[k]=v
}
//獲取操作
func (sm *SynchronizedMap) Get(k interface{}) interface{}{
sm.rw.RLock()
defer sm.rw.RUnlock()
return sm.data[k]
}
//刪除操作
func (sm *SynchronizedMap) Delete(k interface{}) {
sm.rw.Lock()
defer sm.rw.Unlock()
delete(sm.data,k)
}
//遍歷Map,并且把遍歷的值給回調函數,可以讓調用者控制做任何事情
func (sm *SynchronizedMap) Each(cb func (interface{},interface{})){
sm.rw.RLock()
defer sm.rw.RUnlock()
for k, v := range sm.data {
cb(k,v)
}
}
//生成初始化一個SynchronizedMap
func NewSynchronizedMap() *SynchronizedMap{
return &SynchronizedMap{
rw:new(sync.RWMutex),
data:make(map[interface{}]interface{}),
}
}
這個安全的Map被我們定義為一個SynchronizedMap的結構體,這個結構體里有兩個字段,一個是讀寫鎖rw,一個是存儲數據的data,data是map類型。
然后就是給SynchronizedMap定義一些方法,如果這些方法是增刪改的,就要使用寫鎖;如果是只讀的,就使用讀鎖。這樣就保證了我們數據data在多個goroutine下的安全性。
有了這個安全的Map我們就可以在多goroutine下增刪改查數據了,都是安全的。
這里定義了一個Each方法,這個方法很有意思,用過Gradle的都知道,也有類似遍歷Map的方法。這個方法我們可以傳入一個回調函數作為參數,來對我們遍歷的SynchronizedMap數據進行處理,比如我打印SynchronizedMap中的數據。
sm.Each(func(k interface{}, v interface{}) {
fmt.Println(k," is ",v)
}
sm就是一個SynchronizedMap,非常簡潔吧。
以上就是讀寫鎖使用的一個例子。我們可以把這個map數據當成緩存數據,或者當成數據庫,然后使用讀寫鎖進行控制,可以多讀,但是只能有一個寫。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。