Go語言原子操作atomic的使用
概述
在日常開發(fā)中,不可避免的會碰到并發(fā)場景,在Go語言中處理同步的方法通常是使用鎖,但如果是對單一的一個整數(shù)操作,這個時候使用鎖可能會造成更大的性能開銷,而且代碼也失去了美觀與優(yōu)雅。
這個時候我們可以使用Go語言自帶的原子操作,原子操作在Go語言的sync/atomic標準庫里面,原子操作是比其他同步技術更基礎的一種技術,而且原子操作是無鎖的,通常是直接通過CPU指令實現(xiàn)。如果去看其他同步技術的源碼可以看到很多技術都是依賴于原子操作的。
同步問題
在正式介紹原子操作之前先看一段代碼,該代碼中創(chuàng)建了100000個協(xié)程,對一個公共變量x進行累加操作,總共有3個版本的代碼,第一個版本是普通版,第二個版本是加鎖版本,第三個版本是原子操作版本。
var ( x int64 lock sync.Mutex wg sync.WaitGroup ) // 普通版 func add() { x++ wg.Done() } // 互斥鎖版 func mutexAdd() { lock.Lock() x++ lock.Unlock() wg.Done() } // atomic版 func atomicAdd() { atomic.AddInt64(&x, 1) wg.Done() } func main() { start := time.Now() for i := 0; i < 100000; i++ { wg.Add(1) //go add() // 普通版add函數(shù),非并發(fā)安全 //go mutexAdd() // 加鎖版add函數(shù),并發(fā)安全,但是加鎖性能開銷大 go atomicAdd() // 原子操作版add函數(shù),并發(fā)安全,性能優(yōu)于加鎖版 } wg.Wait() end := time.Now() fmt.Println("計算結(jié)果:", x) fmt.Println("消耗時間:", end.Sub(start)) }
依次運行三個版本,得出結(jié)果如下:
# 普通版本
計算結(jié)果: 96725
消耗時間: 26.4237ms# 加鎖版本
計算結(jié)果: 100000
消耗時間: 31.2588ms# 原子操作版本
計算結(jié)果: 100000
消耗時間: 27.3615ms
從上面的結(jié)果可以看出,普通版本的直接結(jié)算結(jié)果就是錯誤的,這個因為普通版本的不是并發(fā)安全的,所以會導致計算錯誤。加鎖版本和原子操作版本都是計算正確,但是原子操作版本所消耗時間要比加鎖版本更低(如果數(shù)字更大相差時間可能會更多,可以自行嘗試)。
atomic
所有的原子操作都在atomic包下面,對于int32,int64,uint32,uint64,uintptr和Pointer類型,都有其對應的原子操作。
func SwapInt32(addr *int32, new int32) (old int32) func SwapInt64(addr *int64, new int64) (old int64) func SwapUint32(addr *uint32, new uint32) (old uint32) func SwapUint64(addr *uint64, new uint64) (old uint64) func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer) func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) func CompareAndSwapInt64(addr *int64, old, new int64) (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) func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool) func AddInt32(addr *int32, delta int32) (new int32) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr) func LoadInt32(addr *int32) (val int32) func LoadInt64(addr *int64) (val int64) func LoadUint32(addr *uint32) (val uint32) func LoadUint64(addr *uint64) (val uint64) func LoadUintptr(addr *uintptr) (val uintptr) func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) func StoreInt32(addr *int32, val int32) func StoreInt64(addr *int64, val int64) func StoreUint32(addr *uint32, val uint32) func StoreUint64(addr *uint64, val uint64) func StoreUintptr(addr *uintptr, val uintptr) func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
以上是atomic包中的所有方法,主要分為5種類型,下面根據(jù)不同類型逐一講解。
Load和Store
Load和Store方法主要用來在并發(fā)環(huán)境下實現(xiàn)對數(shù)字的設置和讀取,Store表示給變量設置一個值,Load表示讀取變量的值。
var value int64 func main() { atomic.StoreInt64(&value, 1) val := atomic.LoadInt64(&value) fmt.Println("value: ", val) }
Add
Add方法更簡單,就是給一個變量加上一個值,使用Add方法加是并發(fā)安全的,不會出現(xiàn)上面示例中普通版本的add函數(shù)一樣出現(xiàn)計算錯誤的問題。如果變量是有符號整數(shù)類型,需要實現(xiàn)對變量的減法,只需要調(diào)用Add方法的時候第二個參數(shù)傳入負數(shù)即可。
Swap和CompareAndSwap
Swap是交換的意思,使用Swap方法可以修改變量的值,同時會將變量的舊值返回。
CompareAndSwap是比較并交換的意思,作用與Swap類似,也是修改變量的值,但是在調(diào)用CompareAndSwap的時候需要傳入需要設置的新值和期望的舊值,如果當前變量的值和期望的舊值一樣,才會將變量修改會新值,同時返回是否修改成功。
var value int64 = 1 func main() { old := atomic.SwapInt64(&value, 2) fmt.Printf("舊值:%d, value:%d\n", old, value) swapped := atomic.CompareAndSwapInt64(&value, 1, 3) fmt.Printf("修改結(jié)果:%t, value: %d\n", swapped, value) swapped = atomic.CompareAndSwapInt64(&value, 2, 3) fmt.Printf("修改結(jié)果:%t, value: %d\n", swapped, value) }
上面的示例中將value設置為1,先使用Swap方法將Value修改為2,同時返回修改前的值。再使用CompareAndSwap想要修改為3,但是因為傳入的期望值1和value的實際值2不相等,所以修改失敗,再次調(diào)用期望值為2且value的實際值為2,則修改成功。運行結(jié)果如下:
舊值:1, value:2
修改結(jié)果:false, value: 2
修改結(jié)果:true, value: 3
新版本結(jié)構(gòu)體類型
在1.19版本,Go語言在atomic中新增了Int32,Int64等結(jié)構(gòu)體類型,使用結(jié)構(gòu)體類型進行原子操作更簡單,不需要再想之前一樣每次從atomic中調(diào)用各種類型的方法來實現(xiàn)原子操作。而是只需要使用結(jié)構(gòu)體的方法即可直接進行原子操作。
var value atomic.Int64 func main() { value.Store(1) fmt.Println("value: ", value.Load()) n := value.Add(1) fmt.Println("value: ", n) old := value.Swap(3) fmt.Printf("舊值:%d, value:%d\n", old, value.Load()) swapped := value.CompareAndSwap(3, 4) fmt.Printf("修改結(jié)果:%t, value:%d\n", swapped, value.Load()) }
上面示例中使用的就是最新的寫法結(jié)構(gòu)體類型,運行結(jié)果如下:
value: 1
value: 2
舊值:2, value:3
修改結(jié)果:true, value:4
到此這篇關于Go語言原子操作atomic的使用的文章就介紹到這了,更多相關Go語言原子操作atomic內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
go?goquery網(wǎng)頁解析實現(xiàn)示例
這篇文章主要為大家介紹了go?goquery網(wǎng)頁解析實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08