Go atomic包是最輕量級的鎖(也稱無鎖結構),可以在不形成臨界區和創建互斥量的情況下完成并發安全的值替換操作,不過這個包只支持int32/int64/uint32/uint64/uintptr這幾種數據類型的一些基礎操作(增減、交換、載入、存儲等)
概念
原子操作僅會由一個獨立的CPU指令代表和完成。原子操作是無鎖的,常常直接通過CPU指令直接實現。 事實上,其它同步技術的實現常常依賴于原子操作。
使用場景
當我們想要對某個變量并發安全的修改,除了使用官方提供的 mutex
,還可以使用 sync/atomic 包的原子操作,它能夠保證對變量的讀取或修改期間不被其他的協程所影響。
atomic 包提供的原子操作能夠確保任一時刻只有一個goroutine對變量進行操作,善用 atomic 能夠避免程序中出現大量的鎖操作。
常見操作
- 增減Add
- 載入Load
- 比較并交換CompareAndSwap
- 交換Swap
- 存儲Store
atomic 操作的對象是一個地址,你需要把可尋址的變量的地址作為參數傳遞給方法,而不是把變量的值傳遞給方法
下面將分別介紹這些操作:
增減操作
此類操作的前綴為 Add
func AddInt32(addr *int32, delta int32) (new int32)func AddInt64(addr *int64, delta int64) (new int64)func AddUint32(addr *uint32, delta uint32) (new uint32)func AddUint64(addr *uint64, delta uint64) (new uint64)func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
需要注意的是,第一個參數必須是指針類型的值,通過指針變量可以獲取被操作數在內存中的地址,從而施加特殊的CPU指令,確保同一時間只有一個goroutine能夠進行操作。
使用舉例:
func add(addr *int64, delta int64) {atomic.AddInt64(addr, delta) //加操作fmt.Println("add opts: ", *addr)
}
載入操作
此類操作的前綴為 Load
func LoadInt32(addr *int32) (val int32)func LoadInt64(addr *int64) (val int64)func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)func LoadUint32(addr *uint32) (val uint32)func LoadUint64(addr *uint64) (val uint64)func LoadUintptr(addr *uintptr) (val uintptr)// 特殊類型: Value類型,常用于配置變更
func (v *Value) Load() (x interface{}) {}
載入操作能夠保證原子的讀變量的值,當讀取的時候,任何其他CPU操作都無法對該變量進行讀寫,其實現機制受到底層硬件的支持。
使用示例:
func load(addr *int64) {fmt.Println("load opts: ", atomic.LoadInt64(&opts))
}
比較并交換
此類操作的前綴為 CompareAndSwap
, 該操作簡稱 CAS,可以用來實現樂觀鎖
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
該操作在進行交換前首先確保變量的值未被更改,即仍然保持參數 old
所記錄的值,滿足此前提下才進行交換操作。CAS的做法類似操作數據庫時常見的樂觀鎖機制。
需要注意的是,當有大量的goroutine 對變量進行讀寫操作時,可能導致CAS操作無法成功,這時可以利用for循環多次嘗試。
使用示例:
func compareAndSwap(addr *int64, oldValue int64, newValue int64) {if atomic.CompareAndSwapInt64(addr, oldValue, newValue) {fmt.Println("cas opts: ", *addr)return}
}
交換
此類操作的前綴為 Swap
:
func SwapInt32(addr *int32, new int32) (old int32)func SwapInt64(addr *int64, new int64) (old int64)func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)func SwapUint32(addr *uint32, new uint32) (old uint32)func SwapUint64(addr *uint64, new uint64) (old uint64)func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
相對于CAS,明顯此類操作更為暴力直接,并不管變量的舊值是否被改變,直接賦予新值然后返回背替換的值。
func swap(addr *int64, newValue int64) {atomic.SwapInt64(addr, newValue)fmt.Println("swap opts: ", *addr)
}
存儲
此類操作的前綴為 Store
:
func StoreInt32(addr *int32, val int32)func StoreInt64(addr *int64, val int64)func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)func StoreUint32(addr *uint32, val uint32)func StoreUint64(addr *uint64, val uint64)func StoreUintptr(addr *uintptr, val uintptr)// 特殊類型: Value類型,常用于配置變更
func (v *Value) Store(x interface{})
此類操作確保了寫變量的原子性,避免其他操作讀到了修改變量過程中的臟數據。
func store(addr *int64, newValue int64) {atomic.StoreInt64(addr, newValue)fmt.Println("store opts: ", *addr)
}
本文節選于Go合集《常見面試題匯總》
GOLANG ROADMAP一個專注Go語言學習、求職的社區。