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