# 5.3 原子操作
`atomic`包中包含了很多原子型操作。它們均基于運行時中`runtime/internal/atomic`的實現。
## 5.3.1 原子操作
原子操作依賴硬件指令的支持,但同時還需要運行時調度器的配合。我們以`atomic.CompareAndSwapPointer`為例,介紹`sync/atomic`包提供的同步模式。
`CompareAndSwapPointer`它在包中只有函數定義,沒有函數體:
```
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
```
其本身由運行時實現。
我們簡單看過了兩個屬于公共包的方法`atomic.Value`和`atomic.CompareAndSwapPointer`, 我們來看一下運行時實現:
```
//go:linkname sync_atomic_CompareAndSwapUintptr sync/atomic.CompareAndSwapUintptr
func sync_atomic_CompareAndSwapUintptr(ptr *uintptr, old, new uintptr) bool
//go:linkname sync_atomic_CompareAndSwapPointer sync/atomic.CompareAndSwapPointer
//go:nosplit
func sync_atomic_CompareAndSwapPointer(ptr *unsafe.Pointer, old, new unsafe.Pointer) bool {
if writeBarrier.enabled {
atomicwb(ptr, new)
}
return sync_atomic_CompareAndSwapUintptr((*uintptr)(noescape(unsafe.Pointer(ptr))), uintptr(old), uintptr(new))
}
```
可以看到`sync_atomic_CompareAndSwapUintptr`函數在運行時中也是沒有方法本體的, 說明其實現由編譯器完成。那么我們來看一下編譯器究竟干了什么:
```
package main
import (
"sync/atomic"
"unsafe"
)
func main() {
var p unsafe.Pointer
newP := 42
atomic.CompareAndSwapPointer(&p, nil, unsafe.Pointer(&newP))
v := (*int)(p)
println(*v)
}
```
編譯結果:
```
TEXT sync/atomic.CompareAndSwapUintptr(SB) /usr/local/Cellar/go/1.11/libexec/src/sync/atomic/asm.s
asm.s:31 0x1001070 e91b0b0000 JMP runtime/internal/atomic.Casuintptr(SB)
:-1 0x1001075 cc INT $0x3
(...)
TEXT runtime/internal/atomic.Casuintptr(SB) /usr/local/Cellar/go/1.11/libexec/src/runtime/internal/atomic/asm_amd64.s
asm_amd64.s:44 0x1001b90 e9dbffffff JMP runtime/internal/atomic.Cas64(SB)
:-1 0x1001b95 cc INT $0x3
(...)
```
可以看到`atomic.CompareAndSwapUintptr`本質上轉到了`runtime/internal/atomic.Cas64`,我們來看一下它的實現:
```
// bool runtime∕internal∕atomic·Cas64(uint64 *val, uint64 old, uint64 new)
// Atomically:
// if(*val == *old){
// *val = new;
// return 1;
// } else {
// return 0;
// }
TEXT runtime∕internal∕atomic·Cas64(SB), NOSPLIT, $0-25
MOVQ ptr+0(FP), BX
MOVQ old+8(FP), AX
MOVQ new+16(FP), CX
LOCK
CMPXCHGQ CX, 0(BX)
SETEQ ret+24(FP)
RET
```
可以看到,實現的本質是使用 CPU 的`LOCK`+`CMPXCHGQ` 指令:首先將 ptr 的值放入 BX,將假設的舊值放入 AX, 要比較的新值放入 CX。然后 LOCK CMPXCHGQ 與累加器 AX 比較并交換 CX 和 BX。
因此原子操作本質上均為使用 CPU 指令進行實現(理所當然)。由于原子操作的方式比較單一,很容易舉一反三, 其他操作不再窮舉。
## 5.3.2 原子值
原子值需要運行時的支持,在原子值進行修改時,Goroutine 不應該被搶占,因此需要鎖定 MP 之間的綁定關系:
```
//go:linkname sync_runtime_procPin sync.runtime_procPin
//go:nosplit
func sync_runtime_procPin() int {
return procPin()
}
//go:nosplit
func procPin() int {
_g_ := getg()
mp := _g_.m
mp.locks++
return int(mp.p.ptr().id)
}
//go:linkname sync_atomic_runtime_procUnpin sync/atomic.runtime_procUnpin
//go:nosplit
func sync_atomic_runtime_procUnpin() {
procUnpin()
}
//go:nosplit
func procUnpin() {
_g_ := getg()
_g_.m.locks--
}
```
原子值`atomic.Value`提供了一種具備原子存取的結構。其自身的結構非常簡單, 只包含一個存放數據的`interface{}`:
type Value struct {
v interface{}
}
```
它僅僅只是對要存儲的值進行了一層封裝。要對這個值進行原子的讀取,依賴`Load`方法:
```
func (v *Value) Load() (x interface{}) {
// 獲得 interface 結構的指針
// 在 go 中,interface 的內存布局有類型指針和數據指針兩部分表示
vp := (*ifaceWords)(unsafe.Pointer(v))
// 獲得存儲值的類型指針
typ := LoadPointer(&vp.typ)
if typ == nil || uintptr(typ) == ^uintptr(0) {
return nil
}
// 獲得存儲值的實際數據
data := LoadPointer(&vp.data)
// 將復制得到的 typ 和 data 給到 x
xp := (*ifaceWords)(unsafe.Pointer(&x))
xp.typ = typ
xp.data = data
return
}
// ifaceWords 定義了 interface{} 的內部表示。
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
```
從這個 Load 方法實際上使用了 Go 運行時類型系統中的`interface{}`這一類型本質上由 兩段內容組成,一個是類型 typ 區域,另一個是實際數據 data 區域。 這個 Load 方法的實現,本質上就是將內部存儲的類型和數據都復制一份并返回。
再來看`Store`。存儲的思路與讀取其實是類似的,但由于類型系統的兩段式表示(typ 和 data) 的存在,存儲操作比讀取操作的實現要更加小心,要考慮當兩個不同的 Goroutine 對兩段值進行寫入時, 如何才能避免寫競爭:
```
func (v *Value) Store(x interface{}) {
if x == nil {
panic("sync/atomic: store of nil value into Value")
}
// Value 存儲值的指針和要存儲的 x 的指針
vp := (*ifaceWords)(unsafe.Pointer(v))
xp := (*ifaceWords)(unsafe.Pointer(&x))
for {
typ := LoadPointer(&vp.typ)
// v 還未被寫入過任何數據
if typ == nil {
// 禁止搶占當前 Goroutine 來確保存儲順利完成
runtime_procPin()
// 先存一個標志位,宣告正在有人操作此值
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
// 如果沒有成功,取消不可搶占,下次再試
runtime_procUnpin()
continue
}
// 如果標志位設置成功,說明其他人都不會向 interface{} 中寫入數據
StorePointer(&vp.data, xp.data)
StorePointer(&vp.typ, xp.typ)
// 存儲成功,再標志位可搶占,直接返回
runtime_procUnpin()
return
}
// 有其他 Goroutine 正在對 v 進行寫操作
if uintptr(typ) == ^uintptr(0) {
continue
}
// 如果本次存入的類型與前次存儲的類型不同
if typ != xp.typ {
panic("sync/atomic: store of inconsistently typed value into Value")
}
// 類型已經寫入,直接保存數據
StorePointer(&vp.data, xp.data)
return
}
}
```
可以看到`atomic.Value`的存取通過`unsafe.Pointer(^uintptr(0))`作為第一次存取的標志位, 當`atomic.Value`第一次寫入數據時,會將當前 Goroutine 設置為不可搶占, 并將要存儲類型進行標記,再存入實際的數據與類型。當存儲完畢后,即可解除不可搶占,返回。
在不可搶占期間,且有并發的 Goroutine 再此存儲時,如果標記沒有被類型替換掉, 則說明第一次存儲還未完成,形成 CompareAndSwap 循環進行等待。
- 第一部分 :基礎篇
- 第1章 Go語言的前世今生
- 1.2 Go語言綜述
- 1.3 順序進程通訊
- 1.4 Plan9匯編語言
- 第2章 程序生命周期
- 2.1 從go命令談起
- 2.2 Go程序編譯流程
- 2.3 Go 程序啟動引導
- 2.4 主Goroutine的生與死
- 第3 章 語言核心
- 3.1 數組.切片與字符串
- 3.2 散列表
- 3.3 函數調用
- 3.4 延遲語句
- 3.5 恐慌與恢復內建函數
- 3.6 通信原語
- 3.7 接口
- 3.8 運行時類型系統
- 3.9 類型別名
- 3.10 進一步閱讀的參考文獻
- 第4章 錯誤
- 4.1 問題的演化
- 4.2 錯誤值檢查
- 4.3 錯誤格式與上下文
- 4.4 錯誤語義
- 4.5 錯誤處理的未來
- 4.6 進一步閱讀的參考文獻
- 第5章 同步模式
- 5.1 共享內存式同步模式
- 5.2 互斥鎖
- 5.3 原子操作
- 5.4 條件變量
- 5.5 同步組
- 5.6 緩存池
- 5.7 并發安全散列表
- 5.8 上下文
- 5.9 內存一致模型
- 5.10 進一步閱讀的文獻參考
- 第二部分 運行時篇
- 第6章 并發調度
- 6.1 隨機調度的基本概念
- 6.2 工作竊取式調度
- 6.3 MPG模型與并發調度單
- 6.4 調度循環
- 6.5 線程管理
- 6.6 信號處理機制
- 6.7 執行棧管理
- 6.8 協作與搶占
- 6.9 系統監控
- 6.10 網絡輪詢器
- 6.11 計時器
- 6.12 非均勻訪存下的調度模型
- 6.13 進一步閱讀的參考文獻
- 第7章 內存分配
- 7.1 設計原則
- 7.2 組件
- 7.3 初始化
- 7.4 大對象分配
- 7.5 小對象分配
- 7.6 微對象分配
- 7.7 頁分配器
- 7.8 內存統計
- 第8章 垃圾回收
- 8.1 垃圾回收的基本想法
- 8.2 寫屏幕技術
- 8.3 調步模型與強弱觸發邊界
- 8.4 掃描標記與標記輔助
- 8.5 免清掃式位圖技術
- 8.6 前進保障與終止檢測
- 8.7 安全點分析
- 8.8 分代假設與代際回收
- 8.9 請求假設與實務制導回收
- 8.10 終結器
- 8.11 過去,現在與未來
- 8.12 垃圾回收統一理論
- 8.13 進一步閱讀的參考文獻
- 第三部分 工具鏈篇
- 第9章 代碼分析
- 9.1 死鎖檢測
- 9.2 競爭檢測
- 9.3 性能追蹤
- 9.4 代碼測試
- 9.5 基準測試
- 9.6 運行時統計量
- 9.7 語言服務協議
- 第10章 依賴管理
- 10.1 依賴管理的難點
- 10.2 語義化版本管理
- 10.3 最小版本選擇算法
- 10.4 Vgo 與dep之爭
- 第12章 泛型
- 12.1 泛型設計的演進
- 12.2 基于合約的泛型
- 12.3 類型檢查技術
- 12.4 泛型的未來
- 12.5 進一步閱讀的的參考文獻
- 第13章 編譯技術
- 13.1 詞法與文法
- 13.2 中間表示
- 13.3 優化器
- 13.4 指針檢查器
- 13.5 逃逸分析
- 13.6 自舉
- 13.7 鏈接器
- 13.8 匯編器
- 13.9 調用規約
- 13.10 cgo與系統調用
- 結束語: Go去向何方?