
1. Go中的原子操作
原子性:一個(gè)或多個(gè)操作在CPU的執(zhí)行過程中不被中斷的特性,稱為原子性。這些操作對(duì)外表現(xiàn)成一個(gè)不可分割的整體,他們要么都執(zhí)行,要么都不執(zhí)行,外界不會(huì)看到他們只執(zhí)行到一半的狀態(tài)。
原子操作:進(jìn)行過程中不能被中斷的操作,原子操作由底層硬件支持,而鎖則是由操作系統(tǒng)提供的API實(shí)現(xiàn),若實(shí)現(xiàn)相同的功能,前者通常會(huì)更有效率
最小案例:
package main
import (
"sync"
"fmt"
)
var count int
func add(wg *sync.WaitGroup) {
defer wg.Done()
count++
}
func main() {
wg := sync.WaitGroup{}
wg.Add(1000)
for i := 0; i < 1000; i++ {
go add(&wg)
}
wg.Wait()
fmt.Println(count)
}
count不會(huì)等于1000,因?yàn)閏ount++這一步實(shí)際是三個(gè)操作:
從內(nèi)存讀取count
CPU更新count = count + 1
寫入count到內(nèi)存
因此就會(huì)出現(xiàn)多個(gè)goroutine讀取到相同的數(shù)值,然后更新同樣的數(shù)值到內(nèi)存,導(dǎo)致最終結(jié)果比預(yù)期少
2. Go中sync/atomic包
Go語(yǔ)言提供的原子操作都是非入侵式的,由標(biāo)準(zhǔn)庫(kù)中sync/aotomic中的眾多函數(shù)代表
atomic包中支持六種類型
int32
uint32
int64
uint64
uintptr
unsafe.Pointer
對(duì)于每一種類型,提供了五類原子操作:
LoadXXX(addr): 原子性的獲取*addr的值,等價(jià)于:
return *addr ? ? ?
StoreXXX(addr, val): 原子性的將val的值保存到*addr,等價(jià)于:
addr = val
AddXXX(addr, delta): 原子性的將delta的值添加到*addr并返回新值(unsafe.Pointer不支持),等價(jià)于:
*addr += delta
return *addr
SwapXXX(addr, new) old: 原子性的將new的值保存到*addr并返回舊值,等價(jià)于:
old = *addr
*addr = new
return old
CompareAndSwapXXX(addr, old, new) bool: 原子性的比較*addr和old,如果相同則將new賦值給*addr并返回true,等價(jià)于:
if *addr == old {
? ? *addr = new
? ? return true
}
return false
因此第一部分的案例可以修改如下,即可通過
// 修改方式1
func add(wg *sync.WaitGroup) {
defer wg.Done()
for {
if atomic.CompareAndSwapInt32(&count, count, count+1) {
break
}
}
}
// 修改方式2
func add(wg *sync.WaitGroup) {
defer wg.Done()
atomic.AddInt32(&count, 1)
}
3. 擴(kuò)大原子操作的適用范圍:atomic.Value
Go語(yǔ)言在1.4版本的時(shí)候向sync/atomic包中添加了新的類型Value,此類型相當(dāng)于一個(gè)容器,被用來(lái)"原子地"存儲(chǔ)(Store)和加載任意類型的值
type Value func(v *Value) Load() (x interface{}): 讀操作,從線程安全的v中讀取上一步存放的內(nèi)容 func(v *Value) Store(x interface{}): 寫操作,將原始的變量x存放在atomic.Value類型的v中
比如作者寫文章時(shí)是22歲,寫著寫著就23歲了..
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
// 此處依舊選用簡(jiǎn)單的數(shù)據(jù)類型,因?yàn)榇a量少
config := atomic.Value{}
config.Store(22)
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go func(i int) {
defer wg.Done()
// 在某一個(gè)goroutine中修改配置
if i == 0 {
config.Store(23)
}
// 輸出中夾雜22,23
fmt.Println(config.Load())
}(i)
}
wg.Wait()
}
4. atomic.Value源碼分析
atomic.Value被設(shè)計(jì)用來(lái)存儲(chǔ)任意類型的數(shù)據(jù),所以它內(nèi)部的字段是一個(gè)interface{}類型
type Value struct {
v interface{}
}
還有一個(gè)ifaceWords類型,作為空interface的內(nèi)部表示格式,typ代表原始類型,data代表真正的值
// ifaceWords is interface{} internal representation.
type ifaceWords struct {
typ? unsafe.Pointer
data unsafe.Pointer
}
4.1 unsafe.Pointer
Go語(yǔ)言并不支持直接操作內(nèi)存,但是它的標(biāo)準(zhǔn)庫(kù)提供一種不保證向后兼容的指針類型unsafe.Pointer, 讓程序可以靈活的操作內(nèi)存,它的特別之處在于:可以繞過Go語(yǔ)言類型系統(tǒng)的檢查
也就是說:如果兩種類型具有相同的內(nèi)存結(jié)構(gòu),我們可以將unsafe.Pointer當(dāng)作橋梁,讓這兩種類型的指針相互轉(zhuǎn)換,從而實(shí)現(xiàn)同一份內(nèi)存擁有兩種解讀方式
例如int類型和int32類型內(nèi)部的存儲(chǔ)結(jié)構(gòu)是一致的,但是對(duì)于指針類型的轉(zhuǎn)換需要這么做:
var a int32
// 獲得a的*int類型指針
(*int)(unsafe.Pointer(&a))
4.2 實(shí)現(xiàn)原子性的讀取任意結(jié)構(gòu)操作
func (v *Value) Load() (x interface{}) {
? ? // 將*Value指針類型轉(zhuǎn)換為*ifaceWords指針類型
vp := (*ifaceWords)(unsafe.Pointer(v))
// 原子性的獲取到v的類型typ的指針
typ := LoadPointer(&vp.typ)
// 如果沒有寫入或者正在寫入,先返回,^uintptr(0)代表過渡狀態(tài),見下文
if typ == nil || uintptr(typ) == ^uintptr(0) {
return nil
}
// 原子性的獲取到v的真正的值data的指針,然后返回
data := LoadPointer(&vp.data)
xp := (*ifaceWords)(unsafe.Pointer(&x))
xp.typ = typ
xp.data = data
return
}
4.3 實(shí)現(xiàn)原子性的存儲(chǔ)任意結(jié)構(gòu)操作
在此之前有一段較為重要的代碼,其中runtime_procPin方法可以將一個(gè)goroutine死死占用當(dāng)前使用的P (此處參考Goroutine調(diào)度器(一):P、M、G關(guān)系, 不發(fā)散了) 不允許其他的goroutine搶占,而runtime_procUnpin則是釋放方法
// Disable/enable preemption, implemented in runtime.
func runtime_procPin()
func runtime_procUnpin()
Store方法
func (v *Value) Store(x interface{}) {
if x == nil {
panic("sync/atomic: store of nil value into Value")
}
// 將現(xiàn)有的值和要寫入的值轉(zhuǎn)換為ifaceWords類型,這樣下一步就能獲取到它們的原始類型和真正的值
vp := (*ifaceWords)(unsafe.Pointer(v))
xp := (*ifaceWords)(unsafe.Pointer(&x))
for {
// 獲取現(xiàn)有的值的type
typ := LoadPointer(&vp.typ)
// 如果typ為nil說明這是第一次Store
if typ == nil {
// 如果你是第一次,就死死占住當(dāng)前的processor,不允許其他goroutine再搶
runtime_procPin()
// 使用CAS操作,先嘗試將typ設(shè)置為^uintptr(0)這個(gè)中間狀態(tài)
// 如果失敗,則證明已經(jīng)有別的線程搶先完成了賦值操作
// 那它就解除搶占鎖,然后重新回到 for 循環(huán)第一步
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
runtime_procUnpin()
continue
}
// 如果設(shè)置成功,說明當(dāng)前goroutine中了jackpot
// 那么就原子性的更新對(duì)應(yīng)的指針,最后解除搶占鎖
StorePointer(&vp.data, xp.data)
StorePointer(&vp.typ, xp.typ)
runtime_procUnpin()
return
}
// 如果typ為^uintptr(0)說明第一次寫入還沒有完成,繼續(xù)循環(huán)等待
if uintptr(typ) == ^uintptr(0) {
continue
}
// 如果要寫入的類型和現(xiàn)有的類型不一致,則panic
if typ != xp.typ {
panic("sync/atomic: store of inconsistently typed value into Value")
}
// 更新data
StorePointer(&vp.data, xp.data)
return
}
}
最后:

上面都是自己整理好的!我就把資料貢獻(xiàn)出來(lái)給有需要的人!順便求一波關(guān)注,哈哈~各位小伙伴關(guān)注我后私信【Java】就可以免費(fèi)領(lǐng)取噠
作者:Takagi_san
鏈接:https://juejin.im/post/5e2c0bbb51882526b645602b