Go 学习笔记(67)— Go 并发安全字典 sync.Map
1. 并發不安全的 map
Go 語言中的 map 在并發情況下,只讀是線程安全的,同時讀寫是線程不安全的。
換句話說,在同一時間段內,讓不同 goroutine 中的代碼,對同一個字典進行讀寫操作是不安全的。字典值本身可能會因這些操作而產生混亂,相關的程序也可能會因此發生不可預知的問題。
package mainimport ("fmt""time"
)func main() {m := map[int]string{1: "haha",}go read(m)time.Sleep(time.Second)go write(m)time.Sleep(30 * time.Second)fmt.Println(m)
}func read(m map[int]string) {for {_ = m[1]time.Sleep(1)}
}func write(m map[int]string) {for {m[1] = "write"time.Sleep(1)}
}
 
執行一段時間后會報錯:
fatal error: concurrent map read and map write
 
2. 并發安全字典 sync.Map
需要并發讀寫時,一般的做法是加鎖,但這樣性能并不高, Go 語言在 1.9 版本中提供了一種效率較高的并發安全的 sync.Map , sync.Map 和 map 不同,不是以語言原生形態提供,而是在 sync 包下的特殊結構。
sync.Map 有以下特性:
- 無須初始化,直接聲明即可。
 sync.Map不能使用map的方式進行取值和設置等操作,而是使用sync.Map的方法進行調用,Store表示存儲,Load表示獲取,Delete表示刪除。- 使用 
Range配合一個回調函數進行遍歷操作,通過回調函數返回內部遍歷出來的值,Range參數中回調函數的返回值在需要繼續迭代遍歷時,返回true,終止迭代遍歷時,返回false。 
Store:存儲一對 key-value 值。
-  
Load:根據 key 獲取對應的 value 值,并且可以判斷 key 是否存在。 -  
LoadOrStore:如果 key 對應的 value 存在,則返回該 value;如果不存在,存儲相應的 value。 -  
Delete:刪除一個 key-value 鍵值對。 -  
Range:循環迭代sync.Map,效果與for range一樣。 
它所有的方法涉及的鍵和值的類型都是 interface{} ,也就是空接口,這意味著可以包羅萬象。所以,我們必須在程序中自行保證它的鍵類型和值類型的正確性。
并發安全的 sync.Map 演示代碼如下:
package mainimport ("fmt""sync"
)func main() {// 聲明 scene,類型為 sync.Map,注意,sync.Map 不能使用 make 創建。var scene sync.Map// 將鍵值對保存到sync.Map// sync.Map 將鍵和值以 interface{} 類型進行保存。scene.Store("greece", 97)scene.Store("london", 100)scene.Store("egypt", 200)// 從sync.Map中根據鍵取值fmt.Println(scene.Load("london"))// 根據鍵刪除對應的鍵值對scene.Delete("london")// 遍歷所有sync.Map中的鍵值對// 遍歷需要提供一個匿名函數,參數為 k、v,類型為 interface{},// 每次 Range() 在遍歷一個元素時,都會調用這個匿名函數把結果返回。scene.Range(func(k, v interface{}) bool {fmt.Println("iterate:", k, v)return true})}
 
輸出結果:
100 true
iterate: greece 97
iterate: egypt 200
 
sync.Map 鍵的實際類型不能是函數類型、字典類型和切片類型。由于這些鍵值的實際類型只有在程序運行期間才能夠確定,所以 Go 語言編譯器是無法在編譯期對它們進行檢查的,不正確的鍵值實際類型肯定會引發 panic。
3. 如何保證并發安全字典中的鍵和值的類型正確性?
3.1 讓并發安全字典只能存儲某個特定類型的鍵。
比如指定這里的鍵只能是 int 類型的,或者只能是字符串,又或是某類結構體。一旦完全確定了鍵的類型,你就可以在進行存、取、刪操作的時候,使用類型斷言表達式去對鍵的類型做檢查了。
一般情況下,這種檢查并不繁瑣。而且,你要是把并發安全字典封裝在一個結構體類型里面,那就更加方便了。你這時完全可以讓 Go 語言編譯器幫助你做類型檢查。
package mainimport ("fmt""sync"
)// IntStrMap 代表鍵類型為int、值類型為string的并發安全字典。
type IntStrMap struct {m sync.Map
}func (iMap *IntStrMap) Delete(key int) {iMap.m.Delete(key)
}func (iMap *IntStrMap) Load(key int) (value string, ok bool) {v, ok := iMap.m.Load(key)if v != nil {value = v.(string)}return
}func (iMap *IntStrMap) LoadOrStore(key int, value string) (actual string, loaded bool) {a, loaded := iMap.m.LoadOrStore(key, value)actual = a.(string)return
}func (iMap *IntStrMap) Range(f func(key int, value string) bool) {f1 := func(key, value interface{}) bool {return f(key.(int), value.(string))}iMap.m.Range(f1)
}func (iMap *IntStrMap) Store(key int, value string) {iMap.m.Store(key, value)
}// pairs 代表測試用的鍵值對列表。
var pairs = []struct {k intv string
}{{k: 1, v: "a"},{k: 2, v: "b"},{k: 3, v: "c"},{k: 4, v: "d"},
}func main() {var iMap IntStrMapiMap.Store(pairs[0].k, pairs[0].v)iMap.Store(pairs[1].k, pairs[1].v)iMap.Store(pairs[2].k, pairs[2].v)fmt.Println("[Three pairs have been stored in the IntStrMap instance]")iMap.Range(func(key int, value string) bool {fmt.Printf("The result of an iteration in Range: %d, %s\n", key, value)return true})k0 := pairs[0].kv0, ok := iMap.Load(k0)fmt.Printf("The result of Load: %v, %v (key: %v)\n", v0, ok, k0)k3 := pairs[3].kv3, ok := iMap.Load(k3)fmt.Printf("The result of Load: %v, %v (key: %v)\n", v3, ok, k3)k2, v2 := pairs[2].k, pairs[2].vactual2, loaded2 := iMap.LoadOrStore(k2, v2)fmt.Printf("The result of LoadOrStore: %v, %v (key: %v, value: %v)\n",actual2, loaded2, k2, v2)v3 = pairs[3].vactual3, loaded3 := iMap.LoadOrStore(k3, v3)fmt.Printf("The result of LoadOrStore: %v, %v (key: %v, value: %v)\n",actual3, loaded3, k3, v3)k1 := pairs[1].kiMap.Delete(k1)fmt.Printf("[The pair with the key of %v has been removed from the IntStrMap instance]\n", k1)v1, ok := iMap.Load(k1)fmt.Printf("The result of Load: %v, %v (key: %v)\n", v1, ok, k1)v1 = pairs[1].vactual1, loaded1 := iMap.LoadOrStore(k1, v1)fmt.Printf("The result of LoadOrStore: %v, %v (key: %v, value: %v)\n",actual1, loaded1, k1, v1)iMap.Range(func(key int, value string) bool {fmt.Printf("The result of an iteration in Range: %d, %s\n", key, value)return true})fmt.Println()
} 
sync.Map 沒有提供獲取 map 數量的方法,替代方法是在獲取 sync.Map 時遍歷自行計算數量。
sync.Map 為了保證并發安全有一些性能損失,因此在非并發情況下,使用 map 相比使用 sync.Map 會有更好的性能。
總結
以上是生活随笔為你收集整理的Go 学习笔记(67)— Go 并发安全字典 sync.Map的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 2022-2028年中国半导体用环氧塑封
 - 下一篇: 2022-2028年中国阻燃母料行业市场