原子操作就是不可中斷的操作,外界是看不到原子操作的中間狀態(tài),要么看到原子操作已經(jīng)完成岸霹,要么看到原子操作已經(jīng)結(jié)束。在某個(gè)值的原子操作執(zhí)行的過(guò)程中将饺,CPU絕對(duì)不會(huì)再去執(zhí)行其他針對(duì)該值的操作贡避,那么其他操作也是原子操作。
Go語(yǔ)言中提供的原子操作都是非侵入式的予弧,在標(biāo)準(zhǔn)庫(kù)代碼包sync/atomic中提供了相關(guān)的原子函數(shù)刮吧。
增或減
用于增或減的原子操作的函數(shù)名稱(chēng)都是以"Add"開(kāi)頭的,后面跟具體的類(lèi)型名掖蛤,比如下面這個(gè)示例就是int64類(lèi)型的原子減操作
func main() {
var counter int64 = 23
atomic.AddInt64(&counter,-3)
fmt.Println(counter)
}
---output---
20
原子函數(shù)的第一個(gè)參數(shù)都是指向變量類(lèi)型的指針杀捻,是因?yàn)樵硬僮餍枰涝撟兞吭趦?nèi)存中的存放位置,然后加以特殊的CPU指令蚓庭,也就是說(shuō)對(duì)于不能取得內(nèi)存存放地址的變量是無(wú)法進(jìn)行原子操作的致讥。第二個(gè)參數(shù)的類(lèi)型會(huì)自動(dòng)轉(zhuǎn)換為與第一個(gè)參數(shù)相同的類(lèi)型。此外彪置,原子操作會(huì)自動(dòng)將操作后的值賦值給變量拄踪,無(wú)需我們自己手動(dòng)賦值了蝇恶。
對(duì)于 atomic.AddUint32() 和 atomic.AddUint64() 的第二個(gè)參數(shù)為 uint32 與 uint64拳魁,因此無(wú)法直接傳遞一個(gè)負(fù)的數(shù)值進(jìn)行減法操作,Go語(yǔ)言提供了另一種方法來(lái)迂回實(shí)現(xiàn):使用二進(jìn)制補(bǔ)碼的特性
注意:unsafe.Pointer 類(lèi)型的值無(wú)法被加減撮弧。
比較并交換(Compare And Swap)
簡(jiǎn)稱(chēng)CAS潘懊,在標(biāo)準(zhǔn)庫(kù)代碼包sync/atomic中以”Compare And Swap“為前綴的若干函數(shù)就是CAS操作函數(shù)姚糊,比如下面這個(gè)
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
第一個(gè)參數(shù)的值是這個(gè)變量的指針,第二個(gè)參數(shù)是這個(gè)變量的舊值授舟,第三個(gè)參數(shù)指的是這個(gè)變量的新值救恨。
運(yùn)行過(guò)程:調(diào)用CompareAndSwapInt32 后,會(huì)先判斷這個(gè)指針上的值是否跟舊值相等释树,若相等肠槽,就用新值覆蓋掉這個(gè)值,若相等奢啥,那么后面的操作就會(huì)被忽略掉秸仙。返回一個(gè) swapped 布爾值,表示是否已經(jīng)進(jìn)行了值替換操作桩盲。
與鎖有不同之處:鎖總是假設(shè)會(huì)有并發(fā)操作修改被操作的值寂纪,而CAS總是假設(shè)值沒(méi)有被修改,因此CAS比起鎖要更低的性能損耗赌结,鎖被稱(chēng)為悲觀鎖捞蛋,而CAS被稱(chēng)為樂(lè)觀鎖。
CAS的使用示例
var value int32
func AddValue(delta int32) {
for {
v:= value
if atomic.CompareAndSwapInt32(&value,v,(v+delta)) {
break
}
}
}
由示例可以看出柬姚,我們需要多次使用for循環(huán)來(lái)判斷該值是否已被更改拟杉,為了保證CAS操作成功,僅在 CompareAndSwapInt32 返回為 true時(shí)才退出循環(huán)量承,這跟自旋鎖的自旋行為相似捣域。
載入與存儲(chǔ)
對(duì)一個(gè)值進(jìn)行讀或?qū)憰r(shí),并不代表這個(gè)值是最新的值宴合,也有可能是在在讀或?qū)懙倪^(guò)程中進(jìn)行了并發(fā)的寫(xiě)操作導(dǎo)致原值改變焕梅。為了解決這問(wèn)題,Go語(yǔ)言的標(biāo)準(zhǔn)庫(kù)代碼包sync/atomic提供了原子的讀蓉郧ⅰ(Load為前綴的函數(shù))或?qū)懭耄⊿tore為前綴的函數(shù))某個(gè)值
將上面的示例改為原子讀取
var value int32
func AddValue(delta int32) {
for {
v:= atomic.LoadInt32(&value)
if atomic.CompareAndSwapInt32(&value,v,(v+delta)) {
break
}
}
}
原子寫(xiě)入總會(huì)成功贞言,因?yàn)樗恍枰P(guān)心原值是什么,而CAS中必須關(guān)注舊值阀蒂,因此原子寫(xiě)入并不能代替CAS该窗,原子寫(xiě)入包含兩個(gè)參數(shù),以下面的StroeInt32為例:
//第一個(gè)參數(shù)是被操作值的指針蚤霞,第二個(gè)是被操作值的新值
func StoreInt32(addr *int32, val int32)
交換
這類(lèi)操作都以”Swap“開(kāi)頭的函數(shù)酗失,稱(chēng)為”原子交換操作“,功能與之前說(shuō)的CAS操作與原子寫(xiě)入操作有相似之處昧绣。
func SwapInt32(addr *int32, new int32) (old int32)
以 SwapInt32 為例规肴,第一個(gè)參數(shù)是int32類(lèi)型的指針,第二個(gè)是新值。原子交換操作不需要關(guān)心原值拖刃,而是直接設(shè)置新值删壮,但是會(huì)返回被操作值的舊值。
原子值
Go語(yǔ)言的標(biāo)準(zhǔn)庫(kù)代碼包sync/atomic中有一個(gè)叫做Value的原子值兑牡,它是一個(gè)結(jié)構(gòu)體類(lèi)型央碟,用于存儲(chǔ)需要原子讀寫(xiě)的值,結(jié)構(gòu)體如下
// Value提供原子加載并存儲(chǔ)一致類(lèi)型的值均函。
// Value的零值從Load返回nil亿虽。
//調(diào)用Store后,不得復(fù)制值苞也。
//首次使用后不得復(fù)制值经柴。
type Value struct {
v interface{}
}
可以看出結(jié)構(gòu)體內(nèi)是一個(gè) v interface{},也就是說(shuō) 該Value原子值可以保存任何類(lèi)型的需要原子讀寫(xiě)的值墩朦。
使用方式如下:
var Atomicvalue atomic.Value
該類(lèi)型有兩個(gè)公開(kāi)的指針?lè)椒?/p>
//原子的讀取原子值實(shí)例中存儲(chǔ)的值坯认,返回一個(gè) interface{} 類(lèi)型的值,且不接受任何參數(shù)氓涣。
//若未曾通過(guò)store方法存儲(chǔ)值之前牛哺,會(huì)返回nil
func (v *Value) Load() (x interface{})
//原子的在原子實(shí)例中存儲(chǔ)一個(gè)值,接收一個(gè) interface{} 類(lèi)型(不能為nil)的參數(shù)劳吠,且不會(huì)返回任何值
func (v *Value) Store(x interface{})
一旦原子值實(shí)例存儲(chǔ)了某個(gè)類(lèi)型的值引润,那么之后Store存儲(chǔ)的值就必須是與該類(lèi)型一致,否則就會(huì)引發(fā)panic痒玩。
嚴(yán)格來(lái)講淳附,atomic.Value類(lèi)型的變量一旦被聲明,就不應(yīng)該被復(fù)制到其他地方蠢古。比如:作為源值賦值給其他變量奴曙,作為參數(shù)傳遞給函數(shù),作為結(jié)果值從函數(shù)返回草讶,作為元素值通過(guò)通道傳遞洽糟,這些都會(huì)造成值的復(fù)制。
但是atomic.Value類(lèi)型的指針類(lèi)型變量就不會(huì)存在這個(gè)問(wèn)題堕战,原因是對(duì)結(jié)構(gòu)體的復(fù)制不但會(huì)生成該值的副本坤溃,還會(huì)生成其中字段的副本,這樣那么并發(fā)引發(fā)的值變化都與原值沒(méi)關(guān)系了嘱丢。
看下面這個(gè)小示例
func main() {
var Atomicvalue atomic.Value
Atomicvalue.Store([]int{1,2,3,4,5})
anotherStore(Atomicvalue)
fmt.Println("main: ",Atomicvalue)
}
func anotherStore(Atomicvalue atomic.Value) {
Atomicvalue.Store([]int{6,7,8,9,10})
fmt.Println("anotherStore: ",Atomicvalue)
}
---output---
anotherStore: {[6 7 8 9 10]}
main: {[1 2 3 4 5]}