Go語(yǔ)言原子操作atomic的使用
概述
在日常開(kāi)發(fā)中,不可避免的會(huì)碰到并發(fā)場(chǎng)景,在Go語(yǔ)言中處理同步的方法通常是使用鎖,但如果是對(duì)單一的一個(gè)整數(shù)操作,這個(gè)時(shí)候使用鎖可能會(huì)造成更大的性能開(kāi)銷,而且代碼也失去了美觀與優(yōu)雅。
這個(gè)時(shí)候我們可以使用Go語(yǔ)言自帶的原子操作,原子操作在Go語(yǔ)言的sync/atomic標(biāo)準(zhǔn)庫(kù)里面,原子操作是比其他同步技術(shù)更基礎(chǔ)的一種技術(shù),而且原子操作是無(wú)鎖的,通常是直接通過(guò)CPU指令實(shí)現(xiàn)。如果去看其他同步技術(shù)的源碼可以看到很多技術(shù)都是依賴于原子操作的。
同步問(wèn)題
在正式介紹原子操作之前先看一段代碼,該代碼中創(chuàng)建了100000個(gè)協(xié)程,對(duì)一個(gè)公共變量x進(jìn)行累加操作,總共有3個(gè)版本的代碼,第一個(gè)版本是普通版,第二個(gè)版本是加鎖版本,第三個(gè)版本是原子操作版本。
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ā)安全,但是加鎖性能開(kāi)銷大
go atomicAdd() // 原子操作版add函數(shù),并發(fā)安全,性能優(yōu)于加鎖版
}
wg.Wait()
end := time.Now()
fmt.Println("計(jì)算結(jié)果:", x)
fmt.Println("消耗時(shí)間:", end.Sub(start))
}
依次運(yùn)行三個(gè)版本,得出結(jié)果如下:
# 普通版本
計(jì)算結(jié)果: 96725
消耗時(shí)間: 26.4237ms# 加鎖版本
計(jì)算結(jié)果: 100000
消耗時(shí)間: 31.2588ms# 原子操作版本
計(jì)算結(jié)果: 100000
消耗時(shí)間: 27.3615ms
從上面的結(jié)果可以看出,普通版本的直接結(jié)算結(jié)果就是錯(cuò)誤的,這個(gè)因?yàn)槠胀ò姹镜牟皇遣l(fā)安全的,所以會(huì)導(dǎo)致計(jì)算錯(cuò)誤。加鎖版本和原子操作版本都是計(jì)算正確,但是原子操作版本所消耗時(shí)間要比加鎖版本更低(如果數(shù)字更大相差時(shí)間可能會(huì)更多,可以自行嘗試)。
atomic
所有的原子操作都在atomic包下面,對(duì)于int32,int64,uint32,uint64,uintptr和Pointer類型,都有其對(duì)應(yīng)的原子操作。
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方法主要用來(lái)在并發(fā)環(huán)境下實(shí)現(xiàn)對(duì)數(shù)字的設(shè)置和讀取,Store表示給變量設(shè)置一個(gè)值,Load表示讀取變量的值。
var value int64
func main() {
atomic.StoreInt64(&value, 1)
val := atomic.LoadInt64(&value)
fmt.Println("value: ", val)
}
Add
Add方法更簡(jiǎn)單,就是給一個(gè)變量加上一個(gè)值,使用Add方法加是并發(fā)安全的,不會(huì)出現(xiàn)上面示例中普通版本的add函數(shù)一樣出現(xiàn)計(jì)算錯(cuò)誤的問(wèn)題。如果變量是有符號(hào)整數(shù)類型,需要實(shí)現(xiàn)對(duì)變量的減法,只需要調(diào)用Add方法的時(shí)候第二個(gè)參數(shù)傳入負(fù)數(shù)即可。
Swap和CompareAndSwap
Swap是交換的意思,使用Swap方法可以修改變量的值,同時(shí)會(huì)將變量的舊值返回。
CompareAndSwap是比較并交換的意思,作用與Swap類似,也是修改變量的值,但是在調(diào)用CompareAndSwap的時(shí)候需要傳入需要設(shè)置的新值和期望的舊值,如果當(dāng)前變量的值和期望的舊值一樣,才會(huì)將變量修改會(huì)新值,同時(shí)返回是否修改成功。
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設(shè)置為1,先使用Swap方法將Value修改為2,同時(shí)返回修改前的值。再使用CompareAndSwap想要修改為3,但是因?yàn)閭魅氲钠谕?和value的實(shí)際值2不相等,所以修改失敗,再次調(diào)用期望值為2且value的實(shí)際值為2,則修改成功。運(yùn)行結(jié)果如下:
舊值:1, value:2
修改結(jié)果:false, value: 2
修改結(jié)果:true, value: 3
新版本結(jié)構(gòu)體類型
在1.19版本,Go語(yǔ)言在atomic中新增了Int32,Int64等結(jié)構(gòu)體類型,使用結(jié)構(gòu)體類型進(jìn)行原子操作更簡(jiǎn)單,不需要再想之前一樣每次從atomic中調(diào)用各種類型的方法來(lái)實(shí)現(xiàn)原子操作。而是只需要使用結(jié)構(gòu)體的方法即可直接進(jìn)行原子操作。
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())
}
上面示例中使用的就是最新的寫(xiě)法結(jié)構(gòu)體類型,運(yùn)行結(jié)果如下:
value: 1
value: 2
舊值:2, value:3
修改結(jié)果:true, value:4
到此這篇關(guān)于Go語(yǔ)言原子操作atomic的使用的文章就介紹到這了,更多相關(guān)Go語(yǔ)言原子操作atomic內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang 獲取系統(tǒng)信息的實(shí)現(xiàn)
本文主要介紹了Golang 獲取系統(tǒng)信息的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
Go語(yǔ)言利用heap實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中heap的使用以及如何利用heap實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列的相關(guān)資料,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-05-05
詳解Go語(yǔ)言Sync.Pool為何不加鎖也能夠?qū)崿F(xiàn)線程安全
在這篇文章中,我們將剖析sync.Pool內(nèi)部實(shí)現(xiàn)中,介紹了sync.Pool比較巧妙的內(nèi)部設(shè)計(jì)思路以及其實(shí)現(xiàn)方式。在這個(gè)過(guò)程中,也間接介紹了為何不加鎖也能夠?qū)崿F(xiàn)線程安全,感興趣的可以學(xué)習(xí)一下2023-04-04
Go+Kafka實(shí)現(xiàn)延遲消息的實(shí)現(xiàn)示例
本文主要介紹了Go+Kafka實(shí)現(xiàn)延遲消息的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
詳解Go語(yǔ)言中pprof性能分析與debug調(diào)試技巧
在Go語(yǔ)言開(kāi)發(fā)中,pprof 性能分析與 debug 技巧是高性能 Go 程序開(kāi)發(fā)的重要部分,下面是詳細(xì)講解,適用于性能瓶頸排查、CPU 和內(nèi)存分析、以及服務(wù)調(diào)試2025-06-06

