原子性:一個或多個操做在CPU的執行過程當中不被中斷的特性,稱爲原子性。這些操做對外表現成一個不可分割的總體,他們要麼都執行,要麼都不執行,外界不會看到他們只執行到一半的狀態。git
原子操做:進行過程當中不能被中斷的操做,原子操做由底層硬件支持,而鎖則是由操做系統提供的API實現,若實現相同的功能,前者一般會更有效率github
最小案例:golang
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
不會等於1000,由於count++
這一步實際是三個操做:api
count
count = count + 1
count
到內存所以就會出現多個goroutine讀取到相同的數值,而後更新一樣的數值到內存,致使最終結果比預期少安全
Go語言提供的原子操做都是非入侵式的,由標準庫中sync/aotomic
中的衆多函數表明bash
atomic包中支持六種類型函數
int32
uint32
int64
uint64
uintptr
unsafe.Pointer
對於每一種類型,提供了五類原子操做:源碼分析
LoadXXX(addr)
: 原子性的獲取*addr
的值,等價於:return *addr
複製代碼
StoreXXX(addr, val)
: 原子性的將val
的值保存到*addr
,等價於:addr = val
複製代碼
AddXXX(addr, delta)
: 原子性的將delta
的值添加到*addr
並返回新值(unsafe.Pointer
不支持),等價於:*addr += delta
return *addr
複製代碼
SwapXXX(addr, new) old
: 原子性的將new
的值保存到*addr
並返回舊值,等價於:old = *addr
*addr = new
return old
複製代碼
CompareAndSwapXXX(addr, old, new) bool
: 原子性的比較*addr
和old
,若是相同則將new
賦值給*addr
並返回true
,等價於:if *addr == old {
*addr = new
return true
}
return false
複製代碼
所以第一部分的案例能夠修改以下,便可經過post
// 修改方式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)
}
複製代碼
Go語言在1.4版本的時候向sync/atomic
包中添加了新的類型Value
,此類型至關於一個容器,被用來"原子地"存儲(Store)和加載任意類型的值優化
atomic.Value
類型的v中好比做者寫文章時是22歲,寫着寫着就23歲了..
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
// 此處依舊選用簡單的數據類型,由於代碼量少
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()
// 在某一個goroutine中修改配置
if i == 0 {
config.Store(23)
}
// 輸出中夾雜22,23
fmt.Println(config.Load())
}(i)
}
wg.Wait()
}
複製代碼
atomic.Value
被設計用來存儲任意類型的數據,因此它內部的字段是一個interface{}
類型
type Value struct {
v interface{}
}
複製代碼
還有一個ifaceWords
類型,做爲空interface的內部表示格式,typ表明原始類型,data表明真正的值
// ifaceWords is interface{} internal representation.
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
複製代碼
Go語言並不支持直接操做內存,可是它的標準庫提供一種不保證向後兼容的指針類型unsafe.Pointer
, 讓程序能夠靈活的操做內存,它的特別之處在於:能夠繞過Go語言類型系統的檢查
也就是說:若是兩種類型具備相同的內存結構,咱們能夠將unsafe.Pointer
看成橋樑,讓這兩種類型的指針相互轉換,從而實現同一分內存擁有兩種解讀方式
例如int類型和int32類型內部的存儲結構是一致的,可是對於指針類型的轉換須要這麼作:
var a int32
// 得到a的*int類型指針
(*int)(unsafe.Pointer(&a))
複製代碼
func (v *Value) Load() (x interface{}) {
// 將*Value指針類型轉換爲*ifaceWords指針類型
vp := (*ifaceWords)(unsafe.Pointer(v))
// 原子性的獲取到v的類型typ的指針
typ := LoadPointer(&vp.typ)
// 若是沒有寫入或者正在寫入,先返回,^uintptr(0)表明過渡狀態,見下文
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
}
複製代碼
在此以前有一段較爲重要的代碼,其中runtime_procPin
方法能夠將一個goroutine死死佔用當前使用的P
(此處參考Goroutine調度器(一):P、M、G關係, 不發散了) 不容許其餘的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")
}
// 將現有的值和要寫入的值轉換爲ifaceWords類型,這樣下一步就能獲取到它們的原始類型和真正的值
vp := (*ifaceWords)(unsafe.Pointer(v))
xp := (*ifaceWords)(unsafe.Pointer(&x))
for {
// 獲取現有的值的type
typ := LoadPointer(&vp.typ)
// 若是typ爲nil說明這是第一次Store
if typ == nil {
// 若是你是第一次,就死死佔住當前的processor,不容許其餘goroutine再搶
runtime_procPin()
// 使用CAS操做,先嚐試將typ設置爲^uintptr(0)這個中間狀態
// 若是失敗,則證實已經有別的線程搶先完成了賦值操做
// 那它就解除搶佔鎖,而後從新回到 for 循環第一步
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
runtime_procUnpin()
continue
}
// 若是設置成功,說明當前goroutine中了jackpot
// 那麼就原子性的更新對應的指針,最後解除搶佔鎖
StorePointer(&vp.data, xp.data)
StorePointer(&vp.typ, xp.typ)
runtime_procUnpin()
return
}
// 若是typ爲^uintptr(0)說明第一次寫入尚未完成,繼續循環等待
if uintptr(typ) == ^uintptr(0) {
continue
}
// 若是要寫入的類型和現有的類型不一致,則panic
if typ != xp.typ {
panic("sync/atomic: store of inconsistently typed value into Value")
}
// 更新data
StorePointer(&vp.data, xp.data)
return
}
}
複製代碼