這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。
前面的有篇文章在講資源競爭的時候,講互斥鎖,互斥鎖的根本就是當一個goroutine訪問的時候,其他goroutine都不能訪問,這樣肯定保證了資源的同步,避免了競爭,不過也降低了效能。
仔細剖析我們的情境,當我們讀取一個資料的時候,如果這個資料永遠不會被修改,那麼其實是不存在資源競爭的問題的,因為資料是不變的,不管怎麼讀取,多少goroutine同時讀取,都是可以的。
所以其實讀取並不是問題,問題主要是修改,修改的資料要同步,這樣其他goroutine才可以感知到。所以真正的互斥應該是讀取和修改、修改和修改之間,讀取和讀取是沒有互斥操作的。
所以這就延伸出來另外一種鎖,叫做讀寫鎖。
讀寫鎖可以讓多個讀操作同時並發,同時讀取,但是對於寫操作是完全互斥的。也就是說,當一個goroutine進行寫操作的時候,其他goroutine既不能進行讀操作,也不能進行寫操作。
1234567891011121314151617181920212223242526272829303132333435 |
var count intvar wg sync.WaitGroupfunc 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 := countfmt.Printf("讀goroutine %d 讀取結束,值為:%d\n", n,v)wg.Done()}func write(n int) {fmt.Printf("寫goroutine %d 正在寫入...\n",n)v := rand.Intn(1000)count = vfmt.Printf("寫goroutine %d 寫入結束,新值為:%d\n", n,v)wg.Done()} |
以上我們定義了一個共用的資源count,並且聲明了2個函數進行讀寫read和write,在main函數的測試中,我們同時啟動了5個讀寫goroutine進行讀寫操作,通過列印的結果來看,寫入操作是處於競爭狀態的,有的寫入操作被覆蓋了。通過go build -race也可以看到更明細的競爭態。
針對這種情況,第一個方案是加互斥鎖,同時智能有一個goroutine可以操作count,但是這種方法效能比較慢,而且我們說的讀操作可以不互斥,所以這種情況比較適合使用讀寫鎖。
12345678910111213141516171819202122232425262728293031323334353637383940 |
var count intvar wg sync.WaitGroupvar rw sync.RWMutexfunc 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 := countfmt.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 = vfmt.Printf("寫goroutine %d 寫入結束,新值為:%d\n", n,v)wg.Done()rw.Unlock()} |
我們在read裡使用讀鎖,也就是RLock和RUnlock,寫鎖的方法名和我們平時使用的一樣Lock和Unlock,這樣,我們就使用了讀寫鎖,可以並發的讀,但是同時只能有一個寫,並且寫的時候不能進行讀操作,現在我們再運行代碼,可以從輸出的資料看到,可以獨到新值了。
我們同時也可以使用go build -race 檢測,也沒有競爭提示了。
《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。
我們在做Java開發的時候,肯定知道SynchronizedMap這個Map,它是一個在多線程下安全的Map,我們可以通過Collections.synchronizedMap(Map<K, V>)來擷取一個安全的Map,下面我們看看如何使用讀寫鎖,基於Go語言來實現一個安全的Map 。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 |
package commonimport ("sync")//安全的Maptype SynchronizedMap struct {rw *sync.RWMutexdata 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)}}//產生初始化一個SynchronizedMapfunc 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中的資料。
123 |
sm.Each(func(k interface{}, v interface{}) {fmt.Println(k," is ",v)}) |
sm就是一個SynchronizedMap,非常簡潔吧。
以上就是讀寫鎖使用的一個例子,我們可以把這個map資料當成快取資料,或者當成資料庫,然後使用讀寫鎖進行控制,可以多讀,但是只能有一個寫。
《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。