Go语言的原子操作atomic
atomic
原子操作
Go中原子操作的支持
CAS
增加或減少
讀取或寫入
原子操作與互斥鎖的區別
參考
atomic
原子操作
原子操作即是進行過程中不能被中斷的操作,針對某個值的原子操作在被進行的過程中,CPU絕不會再去進行其他的針對該值的操作。為了實現這樣的嚴謹性,原子操作僅會由一個獨立的CPU指令代表和完成。原子操作是無鎖的,常常直接通過CPU指令直接實現。 事實上,其它同步技術的實現常常依賴于原子操作。
具體的原子操作在不同的操作系統中實現是不同的。比如在Intel的CPU架構機器上,主要是使用總線鎖的方式實現的。 大致的意思就是當一個CPU需要操作一個內存塊的時候,向總線發送一個LOCK信號,所有CPU收到這個信號后就不對這個內存塊進行操作了。 等待操作的CPU執行完操作后,發送UNLOCK信號,才結束。 在AMD的CPU架構機器上就是使用MESI一致性協議的方式來保證原子操作。 所以我們在看atomic源碼的時候,我們看到它針對不同的操作系統有不同匯編語言
文件。
Go中原子操作的支持
Go語言的sync/atomic提供了對原子操作的支持,用于同步訪問整數和指針。
Go語言提供的原子操作都是非入侵式的
原子操作支持的類型包括int32、int64、uint32、uint64、uintptr、unsafe.Pointer。
CAS
go中的Cas操作,是借用了CPU提供的原子性指令來實現。CAS操作修改共享變量時候不需要對共享變量加鎖,而是通過類似樂觀鎖的方式進行檢查,本質還是不斷的占用CPU 資源換取加鎖帶來的開銷(比如上下文切換開銷)。
原子操作中的CAS(Compare And Swap),在sync/atomic包中,這類原子操作由名稱以CompareAndSwap為前綴的若干個函數提供
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)
調用函數后,CompareAndSwap函數會先判斷參數addr指向的操作值與參數old的值是否相等,僅當此判斷得到的結果是true之后,才會用參數new代表的新值替換掉原先的舊值,否則操作就會被忽略。
我們使用的mutex互斥鎖類似悲觀鎖,總是假設會有并發的操作要修改被操作的值,所以使用鎖將相關操作放入到臨界區加以保存。而CAS操作做法趨于樂觀鎖,總是假設被操作的值未曾改變(即與舊值相等),并一旦確認這個假設的真實性就立即進行值替換。在被操作值被頻繁變更的情況下,CAS操作并不那么容易成功所以需要不斷進行嘗試,直到成功為止。
舉個栗子
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
addValue(10)
fmt.Println("======New value=======")
fmt.Println(value)
}
//不斷地嘗試原子地更新value的值,直到操作成功為止
func addValue(delta int32) {
for {
v := value
if atomic.CompareAndSwapInt32(&value, v, v+delta) {
break
}
}
}
這一系列的函數需要比較后再進行交換,也有不需要進行比較就進行交換的原子操作。
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)
競爭條件是由于異步的訪問共享資源,并試圖同時讀寫該資源而導致的,使用互斥鎖和通道的思路都是在線程獲得到訪問權后阻塞其他線程對共享內存的訪問,而使用原子操作解決數據競爭問題則是利用了其不可被打斷的特性。
增加或減少
對一個數值進行增加或者減少的行為也需要保證是原子的,它對應于atomic包的函數就是
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)
讀取或寫入
當我們要讀取一個變量的時候,很有可能這個變量正在被寫入,這個時候,我們就很有可能讀取到寫到一半的數據。 所以讀取操作是需要一個原子行為的。
在atomic包中就是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)
讀取是有原子性的操作的,同樣寫入atomic包也提供了相關的操作包。
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)
原子操作與互斥鎖的區別
首先atomic操作的優勢是更輕量,比如CAS可以在不形成臨界區和創建互斥量的情況下完成并發安全的值替換操作。這可以大大的減少同步對程序性能的損耗。
原子操作也有劣勢。還是以CAS操作為例,使用CAS操作的做法趨于樂觀,總是假設被操作值未曾被改變(即與舊值相等),并一旦確認這個假設的真實性就立即進行值替換,那么在被操作值被頻繁變更的情況下,CAS操作并不那么容易成功。而使用互斥鎖的做法則趨于悲觀,我們總假設會有并發的操作要修改被操作的值,并使用鎖將相關操作放入臨界區中加以保護。
下面是幾點區別:
互斥鎖是一種數據結構,用來讓一個線程執行程序的關鍵部分,完成互斥的多個操作
原子操作是無鎖的,常常直接通過CPU指令直接實現
原子操作中的cas趨于樂觀鎖,CAS操作并不那么容易成功,需要判斷,然后嘗試處理
可以把互斥鎖理解為悲觀鎖,共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程
atomic包提供了底層的原子性內存原語,這對于同步算法的實現很有用。這些函數一定要非常小心地使用,使用不當反而會增加系統資源的開銷,對于應用層來說,最好使用通道或sync包中提供的功能來完成同步操作。
針對atomic包的觀點在Google的郵件組里也有很多討論,其中一個結論解釋是:
應避免使用該包裝?;蛘撸喿xC ++ 11標準的“原子操作”一章;如果您了解如何在C ++中安全地使用這些操作,那么你才能有安全地使用Go的sync/atomic包的能力。
參考
【Go并發編程之美-CAS操作】https://zhuanlan.zhihu.com/p/56733484
【sync/atomic - 原子操作】https://docs.kilvn.com/The-Golang-Standard-Library-by-Example/chapter16/16.02.html
【Go語言的原子操作和互斥鎖的區別】https://studygolang.com/articles/29240
【Package atomic】https://go-zh.org/pkg/sync/atomic/
總結
以上是生活随笔為你收集整理的Go语言的原子操作atomic的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: golang幽灵蛛(pholcus)(一
- 下一篇: kafka中partition的概念,解