Golang基礎進階——並發Map和List
sync.Map
Go 語言中 map 在並發情況下,只讀是線程安全的,同時讀寫線程不安全。
下面來看下並發情況下讀 map 出現的問題,示例:
func main() {
for {
m := make(map[int]int)
// 開啟一段並發代碼
go func() {
// 不停地對map進行寫入
for {
m[1] = 1
}
}()
go func() {
// 不停地對map進行讀取
for {
_ = m[1]
}
}()
}
}
// fatal error: concurrent map read and map write
運行時輸出提示並發 map 讀寫。 也就是說使用了兩個並發函數不斷地對map行讀和寫而發生了競態問題。 map 內部對這種並發操作進行檢查井提前發現。
需要並發讀寫 ,一般的做法是加鎖,但這樣性能並不高。 Go 語言 1.9 本中提了一種效率較高的並發安全 sync.Map。sync.Map 和 map 不同,不是以語言原生形態提供,而是在 sync 包下特殊結構。
sync.Map 以下特性:
無需初始化,直接聲明即可。
sync.Map 不能使用 map 方式進行取值和設置等操作,而是使用 sync.Map 方法進行調用 Store 表示存儲,Load 表示獲取 Delete 表示刪除。使用 Range 配合一個回調函數進行遍歷操作,通過回調函數返回內部遍歷出來的值。Range 參數中的回調函數的返回值功能是:需要繼續迭代遍歷時,返回 true;終止迭代遍歷時,返回 false。
示例:
func main() {
// 聲明 scene 類型為 sync.Map 。注意,sync Map 不能使用 make 創建。
var scene sync.Map
// 保存鍵值,是以interface{}類型進行保存
scene.Store("green",97)
scene.Store("london",100)
scene.Store("egypt",200)
// 從sync.Map中根據鍵取值
fmt.Println(scene.Load("london"))
//根據鍵刪除對應的鍵值對
scene.Delete("london")
scene.Range(func(key, value interface{}) bool {
fmt.Println("iterate: ",key, value)
return true
})
}
sync 沒有提供獲取 map 數的方法,替代方法是獲取時遍歷自行計算數量。 sync.Map為了保證並發安全有一些性能損失,因此在非並發情況下,使用 map 相比使用 sync.Map
會有更好的性能。
列表( list )————可以快速增刪的非連續空間的容器
概述
列表是一種非連續存儲的容器,由多個節點組成,節點通過一些變量記錄彼此之間的關系。列表有很多種實現方法,如單鏈表、雙鏈表等。
列表的原理可以這樣理解:假設A、B、C個人都有電話號碼,如果A把號碼告訴B, B把號碼告訴給C ,這個過程就建立了y一個單鏈表結構,如圖所示:

如果在這個基礎上,再從C開始將自己的號碼給自己知道號碼的人,這樣就形成了雙鏈表結構,如圖所示:

那么如果需要獲得所有人的號碼,只需要從A或者C開始,要求他們將自己的號碼發出來,然后再通知下一個人如此循環。這個過程就是列表遍歷。
如果B換號碼了,他需要通知A和C,將自己的號碼移除。這個過程就是列表元素的刪除操作,如圖所示:

在 Go 語言中,將列表使用 container/list 包來實現,內部的實現原理是雙鏈表。列表高效地進行任意位置的元素插入和刪除操作。
初始化列表
list 的初始化有兩種方法: New 和聲明。兩種方法的初始化效果都是一致的。
1. 通過container/list包的New方法初始化list
變量名 := list.New()
2. 通過聲明初始化list
var 變量名 list.List
列表與切片和map不同的是,列表並沒有具體元素類型的限制。因此,列表的元素可以是任意類型。這既帶來便利,也會引來一些問題。給一個列表放入了非期望類型的值,
在取出值后,將 interface{} 轉換為期望類型時將會發生岩機。
在列表中插入元素
雙鏈表支持從隊列前方或后方插入元素,分別對應的方法是 PushFront 和 PushBack
提示:這兩個方法都會返回一個 *list.Element 結構。如果在以后的使用中需要刪除插入的元素,則只能通過*list.Element 配合Remove()方法進行刪除這種方法可以讓刪除更加效率化,也是雙鏈表特性之一。
下面代碼展示如何給 list 添加元素:
func main() {
l := list.New()
// 將 first 字符串插入到列表的尾部,此時列表是空的,插入后只有一個元素
l.PushBack("first")
// 將數值67放入列表。此時,列表中己經存在 first 元素, 67 這個元素將被放在 fist 的前面
l.PushFront("67")
}
列表插入元素的方法如表所示。

從列表中刪除元素
列表的插入函數的返回值會提供一個 *list.Element結構,這個結構記錄着列表元素的值及和其他節點之間的關系等信息。 從列表中刪除元素時,需要用到這個結構進行快速刪除。
func main() {
l := list.New()
// 尾部添加
l.PushBack("canon")
// 頭部添加67
l.PushFront(67)
// 尾部添加后保存元素句柄,並將這個元素的內部結構保存到 lement 變量中
element := l.PushBack("first")
// 在first之后添加high
l.InsertAfter("heigh", element)
// 在“fist”之前添加”noon”
l.InsertBefore("noon", element)
// 移除 element 變量對應的元素
l.Remove(element)
}
下面列表展示了每次操作列表的實際使用情況。

遍歷列表——訪問列表的每一個元素
遍歷雙鏈表需要配合 Front() 函數獲取頭元素,遍歷時只要元素不為空就可以繼續進行。每一次遍歷調用元素的 Next,示例:
func main() {
l := list.New()
l.PushBack("canon")
l.PushFront(67)
for i := l.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
}
使用 for 語句進行遍歷,其中 i:= I.Front() 表示初始賦值,只會在一開始執行一次; 每次循環會進行 i != nil 語句判斷,如果返回 false ,表示退出循環,反之則會執行 i = i.Next()。使用遍歷返回的 *list.Element 的Value 成員取得放入列表時的原值。
