> 原子操作即是進行過程中不能被中斷的操作。針對某個值的原子操作在被進行的過程中,CPU絕不會再去進行其他的針對該值的操作。
為了實現這樣的嚴謹性,原子操作僅會由一個獨立的CPU指令代表和完成。
- GO語言提供的原子操作都是非入侵式的,由標準庫sync/atomic中的眾多函數代表
- 類型包括int32,int64,uint32,uint64,uintptr,unsafe.Pointer,共六個。
- 這些函數提供的原子操作共有五種:增或減,比較并交換,載入,存儲和交換
# int各種類型取值范圍
| 類型 | 長度(字節) | 值范圍 |
|-----|-----|-----|
| int8 | 1 | -128 ~ 127 |
| uint8(byte) | 1 | 0 ~ 255 |
| int16 | 2 | 32768~32767 |
| uint16 | 2 | 0~65535 |
| int32 | 4 | 2147483648~2147483647 |
| uint32 | 4 | 0~4294967295 |
| int64 | 8 | -9223372036854775808~9223372036854775807 |
| uint64 | 8 | 0~18446744073709551615 |
| int | 平臺相關 | 平臺相關 |
| uint | 平臺相關 | 平臺相關 |
| uintptr | 同指針 | 在32位平 下為4字節,64位平 下為8字節 |
# 增或減Add
函數名稱都以Add為前綴,并后跟針對的具體類型的名稱。
- 被操作的類型只能是數值類型
- int32,int64,uint32,uint64,uintptr類型可以使用原子增或減操作
- 第一個參數值必須是一個指針類型的值,以便施加特殊的CPU指令
- 第二個參數值的類型和第一個被操作值的類型總是相同的。
### 示例
~~~
package main
import (
"fmt"
"sync/atomic"
)
func main(){
var i32 int32
fmt.Println("=====old i32 value=====")
fmt.Println(i32)
//第一個參數值必須是一個指針類型的值,因為該函數需要獲得被操作值在內存中的存放位置,以便施加特殊的CPU指令
//結束時會返回原子操作后的新值
newI32 := atomic.AddInt32(&i32,3)
fmt.Println("=====new i32 value=====")
fmt.Println(i32)
fmt.Println(newI32)
var i64 int64
fmt.Println("=====old i64 value=====")
fmt.Println(i64)
newI64 := atomic.AddInt64(&i64,-3)
fmt.Println("=====new i64 value=====")
fmt.Println(i64)
fmt.Println(newI64)
}
~~~
結果:
/usr/local/go/bin/go run /Users/liuxinming/go/src/free/learngo/atomic/add/add.go
=====old i32 value=====
0
=====new i32 value=====
-3
-3
=====old i64 value=====
0
=====new i64 value=====
-3
-3
# 比較并交換CAS
Compare And Swap 簡稱CAS,在sync/atomic包種,這類原子操作由名稱以‘CompareAndSwap’為前綴的若干個函數代表。
- 聲明如下
~~~
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
~~~
- 調用函數后,會先判斷參數addr指向的被操作值與參數old的值是否相等
- 僅當此判斷得到肯定的結果之后,才會用參數new代表的新值替換掉原先的舊值,否則操作就會被忽略。
-
so, 需要用for循環不斷進行嘗試,直到成功為止
-
使用鎖的做法趨于悲觀
- 我們總假設會有并發的操作要修改被操作的值,并使用鎖將相關操作放入臨界區中加以保護
- 使用CAS操作的做法趨于樂觀
- 總是假設被操作值未曾被改變(即與舊值相等),并一旦確認這個假設的真實性就立即進行值替換。
### 示例
~~~
package main
import (
"fmt"
"sync/atomic"
)
var value int32
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
fmt.Println("======CAS value=======")
addValue(3)
fmt.Println(value)
}
//不斷地嘗試原子地更新value的值,直到操作成功為止
func addValue(delta int32){
//在被操作值被頻繁變更的情況下,CAS操作并不那么容易成功
//so 不得不利用for循環以進行多次嘗試
for {
v := value
if atomic.CompareAndSwapInt32(&value, v, (v + delta)){
//在函數的結果值為true時,退出循環
break
}
//操作失敗的緣由總會是value的舊值已不與v的值相等了.
//CAS操作雖然不會讓某個Goroutine阻塞在某條語句上,但是仍可能會使流產的執行暫時停一下,不過時間大都極其短暫.
}
}
~~~
結果:
======old value=======
0
======CAS value=======
3
# 載入Load
上面的比較并交換案例總 v:= value為變量v賦值,但… 要注意,在進行讀取value的操作的過程中,其他對此值的讀寫操作是可以被同時進行的,那么這個讀操作很可能會讀取到一個只被修改了一半的數據.
- so so so , 我們要使用sync/atomic代碼包同樣為我們提供了一系列的函數,以Load為前綴(載入),來確保這樣的糟糕事情發生。
### 示例
~~~
package main
import (
"fmt"
"sync/atomic"
)
var value int32
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
fmt.Println("======CAS value=======")
addValue(3)
fmt.Println(value)
}
//不斷地嘗試原子地更新value的值,直到操作成功為止
func addValue(delta int32){
//在被操作值被頻繁變更的情況下,CAS操作并不那么容易成功
//so 不得不利用for循環以進行多次嘗試
for {
//v := value
//在進行讀取value的操作的過程中,其他對此值的讀寫操作是可以被同時進行的,那么這個讀操作很可能會讀取到一個只被修改了一半的數據.
//因此我們要使用載入
v := atomic.LoadInt32(&value)
if atomic.CompareAndSwapInt32(&value, v, (v + delta)){
//在函數的結果值為true時,退出循環
break
}
//操作失敗的緣由總會是value的舊值已不與v的值相等了.
//CAS操作雖然不會讓某個Goroutine阻塞在某條語句上,但是仍可能會使流產的執行暫時停一下,不過時間大都極其短暫.
}
}
~~~
- atomic.LoadInt32接受一個*int32類型的指針值
- 返回該指針指向的那個值
# 存儲Store
> 與讀取操作相對應的是寫入操作。 而sync/atomic包也提供了與原子的載入函數相對應的原子的值存儲函數。 以Store為前綴
- 在原子地存儲某個值的過程中,任何CPU都不會進行針對同一個值的讀或寫操作。
- 原子的值存儲操作總會成功,因為它并不會關心被操作值的舊值是什么
- 和CAS操作有著明顯的區別
~~~
fmt.Println("======Store value=======")
atomic.StoreInt32(&value, 10)
fmt.Println(value)
~~~
# 交換Swap
- 與CAS操作不同,原子交換操作不會關心被操作的舊值。
- 它會直接設置新值
- 它會返回被操作值的舊值
- 此類操作比CAS操作的約束更少,同時又比原子載入操作的功能更強
# 實際案例(繼續改造上一版代碼)
~~~
//數據文件的實現類型
type myDataFile struct {
f *os.File //文件
fmutex sync.RWMutex //被用于文件的讀寫鎖
rcond *sync.Cond //讀操作需要用到的條件變量
woffset int64 // 寫操作需要用到的偏移量
roffset int64 // 讀操作需要用到的偏移量
dataLen uint32 //數據塊長度
}
//此處省略...
func (df *myDataFile) Read() (rsn int64, d Data, err error){
// 讀取并更新讀偏移量
var offset int64
for {
offset = atomic.LoadInt64(&df.roffset)
if atomic.CompareAndSwapInt64(&df.roffset, offset, (offset + int64(df.dataLen))){
break
}
}
//讀取一個數據塊,最后讀取的數據塊序列號
rsn = offset / int64(df.dataLen)
bytes := make([]byte, df.dataLen)
//讀寫鎖:讀鎖定
df.fmutex.RLock()
defer df.fmutex.RUnlock()
for {
_, err = df.f.ReadAt(bytes, offset)
if err != nil {
if err == io.EOF {
//暫時放棄fmutex的 讀鎖,并等待通知的到來
df.rcond.Wait()
continue
}
}
break
}
d = bytes
return
}
func (df *myDataFile) Write(d Data) (wsn int64, err error){
//讀取并更新寫的偏移量
var offset int64
for {
offset = atomic.LoadInt64(&df.woffset)
if atomic.CompareAndSwapInt64(&df.woffset, offset, (offset + int64(df.dataLen))){
break
}
}
//寫入一個數據塊,最后寫入數據塊的序號
wsn = offset / int64(df.dataLen)
var bytes []byte
if len(d) > int(df.dataLen){
bytes = d[0:df.dataLen]
}else{
bytes = d
}
df.fmutex.Lock()
defer df.fmutex.Unlock()
_, err = df.f.Write(bytes)
//發送通知
df.rcond.Signal()
return
}
func (df *myDataFile) Rsn() int64{
offset := atomic.LoadInt64(&df.roffset)
return offset / int64(df.dataLen)
}
func (df *myDataFile) Wsn() int64{
offset := atomic.LoadInt64(&df.woffset)
return offset / int64(df.dataLen)
}
~~~
完整代碼放在GITHUB:
[https://github.com/lxmgo/learngo](https://github.com/lxmgo/learngo)
一些學習過程整理,希望對大家學習Go語言有所幫助。
- 前言
- golang學習(一)之安裝
- Go語言學習二:Go基礎(變量、常量、數值類型、字符串、錯誤類型)
- Go語言學習三:Go基礎(iota,array,slice,map,make,new)
- Go語言學習四:struct類型
- Ubuntu 14.04/CentOS 6.5中安裝GO LANG(GO語言)
- Mac OS 安裝golang
- Mac install Thrift
- Thrift RPC 使用指南實戰(附golang&PHP代碼)
- golang net/http包使用
- 冒泡排序Bubble sort-golang
- 快速排序Quick sort - golang
- Go語言學習:Channel是什么?
- Golang的select/非緩沖的Channel實例詳解
- Golang time包的定時器/斷續器
- Golang同步:鎖的使用案例詳解
- Golang同步:條件變量和鎖組合使用
- Golang同步:原子操作使用
- Golang之bytes.buffer
- Golang之字符串格式化
- Golang之反射reflect包
- Go語言配置文件解析器,類似于Windows下的INI文件.