鎖
- 重複加鎖會引發死鎖,重複解鎖會引發panic
sync.Mutex
作爲參數時的時候需要傳指針,不然就是拷貝,會引起加鎖失敗
條件變量
- 條件變量並不是被用來保護臨界區和共享資源的,它是用於控制想要訪問共享資源線程的順序
- 當共享資源的狀態發生變化時,它可以被用來通知被互斥鎖阻塞的線程
- 條件變量是基於互斥鎖或讀寫鎖的,必須有他們的支持才能夠起作用
原子操作
- 執行速度要比其他的同步工具快得多,通常會高出好幾個數量級
- 但由於原子操作函數只支持非常有限的數據類型,所以在很多應用場景下,互斥鎖往往是更加適合的
- 操作系統層面只對針對二進制位或整數的原子操作提供了支持
- 因爲原子操作不能被中斷,所以它需要足夠簡單,並且要求快速
- 如果原子操作遲遲不能完成,而它又不會被中斷,將會給計算機執行指令的效率帶來巨大的負面影響
- 操作系統層面只對針對二進制位或整數的原子操作提供了支持
sync/atomic
包中的函數可以做的原子操作有: 加法(add)、比較並交換(compare and swap,簡稱 CAS)、加載(load)、存儲(store)和交換(swap)- CAS 操作,是有條件的交換操作,只有在條件滿足的情況下才會進行值的交換
WaitGroup
- 適合實現一對多的 goroutine 協作流程
sync.waitGroup
作爲參數時,應該傳入指針,或者Done()
方法- 計數器的值不能小於0, 這樣會引發一個 panic
- 不適當地調用這類值的Done方法和Add方法都會如此
func main() {
var wg sync.WaitGroup
wg.Add(2)
go f(&wg)
go g(wg.Done)
wg.Wait()
}
func f(wg *sync.WaitGroup) {
defer wg.Done()
//do something
}
func g(done func()) {
defer done()
//do something
}
併發安全的map
-
map多線程不安全,多個線程同時寫入map會panic
- 並且不能被recover
-
解決方案
- 使用
sync.RWMutex
加鎖- 不推薦,鎖消耗較大
- 使用線程安全的
sync.Map
- 適合讀多寫少
- 用空間換時間,內存佔用較大
- 算法複雜度與map類型一樣都是O(1)
- 使用 concurrent map
- 適合讀寫都頻繁
- 分段加鎖map
- 使用
-
sync.Map
的鍵和值的類型都是interface{}
- 由於這些鍵值的實際類型只有在程序運行期間才能夠確定,所以無法在編譯期對它們進行檢查
- 鍵類型不能是函數類型、字典類型和切片類型
- 不正確的鍵值實際類型會引發 panic
-
實現原理
- 本身確實也用到了鎖,但是,它會盡可能地避免使用鎖
sync.Map
類型在內部使用了大量的原子操作來存取鍵和值,並使用了兩個原生的map作爲存儲介質- 這兩個原生字典一個被稱爲只讀字典,另一個被稱爲髒字典
- 空間換時間。通過冗餘的兩個數據結構(read、dirty),實現加鎖對性能的影響
- 使用只讀數據(read),避免讀寫衝突
- 動態調整,miss次數多了之後,將dirty數據提升爲read
- 延遲刪除。 刪除一個鍵值只是打標記,只有在提升dirty的時候才清理刪除的數據
- 優先從read讀取、更新、刪除,因爲對read的讀取不需要鎖