atomic
atomic是go提供的一個(gè)執(zhí)行原子操作的包,雖然提供了這個(gè)包,但是go官方并不是很推薦使用;
除了做一些低級(jí)的應(yīng)用程序外,go更推薦使用通道和sync來(lái)處理;
PS: go中的sync.Mutex底層都是通過(guò)atomic來(lái)實(shí)現(xiàn)的;這么看atomic確實(shí)比較低級(jí)。
雖然它比較底層,但是我還是有必要了解使用它。
1. 什么是原子操作呢?
可以這么簡(jiǎn)單的理解,在程序執(zhí)行某一個(gè)操作的時(shí)候,在計(jì)算機(jī)底層其實(shí)是分了多個(gè)步驟去處理它;
有多個(gè)步驟處理,那么就意味著有中間狀態(tài)(操作中、沒(méi)操作完的狀態(tài));
而原子操作,它是一個(gè)不可分割的整體,沒(méi)有中間狀態(tài),要么成功了、要么失敗了。,它通過(guò)底層的cpu操作去實(shí)現(xiàn)。
這樣有什么好處,在多goroutine并發(fā)操作的同一個(gè)數(shù)據(jù)的時(shí)候,可以保護(hù)數(shù)據(jù)的一致性。
2. atomic 概述
在golang中,atomic主要提供了下面幾種操作:
- Store - 存一個(gè)值
- Load - 獲取一個(gè)值
- Swap - 更新一個(gè)值(返回舊值)
- Add - 加值
- CompareAndSwap - 比較然后更新值(如果值還是原來(lái)的值,則更新)
另外主要是對(duì)下面的類(lèi)型實(shí)現(xiàn)原子操作:
int32int64uint32unint64uintptr
函數(shù)格式為操作 + 類(lèi)型,比如:
對(duì)于int32,會(huì)有下列函數(shù):
atomic.AddInt32()atomic.StoreInt32()atomic.LoadInt32()atomic.SwapInt32()-
atomic.CompareAndSwapInt32(),其它類(lèi)型同理。
雖然go提供了函數(shù)操作,但是go鼓勵(lì)我們采用,方法調(diào)用(更人性化、不臃腫).
比如atomic.AddInt32()對(duì)應(yīng)的方法為Add()非常簡(jiǎn)潔哦~
3. 函數(shù)調(diào)用方式
需要注意的是:atomic操作的都是地址
由于每種類(lèi)型使用方式基本差不多,這里拿int32舉例演示
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var a int32
// 將1 存給a變量
atomic.StoreInt32(&a, 1)
fmt.Println(a)
fmt.Println(atomic.LoadInt32(&a)) // 讀取a地址的值
// 更新值
oldValue := atomic.SwapInt32(&a, 2)
fmt.Println("新值:", a, "舊值:", oldValue)
// 添加值
atomic.AddInt32(&a, 2) // 加2
fmt.Println("增加后值:", a)
atomic.AddInt32(&a, -1) // 減1
fmt.Println("減少后值:", a)
// 比較后更新 返回是否更新成功
swapped := atomic.CompareAndSwapInt32(&a, 3, 6) // 如果舊值是3 則更新為6
fmt.Println("第一次比較更新是否成功:", swapped, "當(dāng)前值為:", a)
swapped = atomic.CompareAndSwapInt32(&a, 4, 3) // 如果舊值是4 則更新為3
fmt.Println("第二次比較更新是否成功: ", swapped, "當(dāng)前值為:", a)
}
// 1
// 1
// 新值: 2 舊值: 1
// 增加后值: 4
// 減少后值: 3
// 第一次比較更新是否成功: true 當(dāng)前值為: 6
// 第二次比較更新是否成功: false 當(dāng)前值為: 6
4. 方法調(diào)用方式
go推薦使用這種,簡(jiǎn)潔。
直接將函數(shù)調(diào)用方式改成方法調(diào)用方式,如下:
package main
import (
"fmt"
"sync/atomic"
)
func main() {
// 使用atomic內(nèi)置的類(lèi)型(結(jié)構(gòu)體)
// 注意這個(gè)時(shí)候取值 要用a.Load()方式
var a atomic.Int32
// 將1 存給a變量
a.Store(1)
fmt.Println(a.Load()) // 讀取a地址的值
// 更新值
oldValue := a.Swap(2)
fmt.Println("新值:", a.Load(), "舊值:", oldValue)
// 添加值
a.Add(2) // 加2
fmt.Println("增加后值:", a.Load())
a.Add(-1) // 減1
fmt.Println("減少后值:", a.Load())
// 比較后更新 返回是否更新成功
swapped := a.CompareAndSwap(3, 6) // 如果舊值是3 則更新為6
fmt.Println("第一次比較更新是否成功:", swapped, "當(dāng)前值為:", a.Load())
swapped = a.CompareAndSwap(4, 3) // 如果舊值是4 則更新為3
fmt.Println("第二次比較更新是否成功: ", swapped, "當(dāng)前值為:", a.Load())
}
看看是不是簡(jiǎn)介了很多。
5. 無(wú)符號(hào)類(lèi)型做減法
對(duì)于有符號(hào)類(lèi)型,Add時(shí)候直接給一個(gè)負(fù)數(shù)是沒(méi)有問(wèn)題的;但是對(duì)于無(wú)符號(hào)類(lèi)型比如(uint32,uint64)不能直接給負(fù)數(shù)——無(wú)符號(hào)沒(méi)有負(fù)數(shù),這個(gè)時(shí)候需要轉(zhuǎn)換下,
怎么轉(zhuǎn)換下呢?
在A(yíng)dd的時(shí)候,加一個(gè)按位取反,然后+1的數(shù)
下面看代碼:
package main
import (
"fmt"
"sync/atomic"
)
func main() {
// 無(wú)符號(hào)類(lèi)型整數(shù)
var a atomic.Uint32
// 將1 存給a變量
a.Store(10)
fmt.Println(a.Load()) // 讀取a地址的值
// 添加值
a.Add(2) // 加2
fmt.Println("增加后值:", a.Load()) // 加完后當(dāng)前為 12
a.Add(^uint32(3) + 1) // 減3 按位取反 然后加1
fmt.Println("第一次減少后值:", a.Load())
// 減少1
a.Add(^uint32(1) + 1)
fmt.Println("第二次減少后值: ", a.Load())
// 再次減少1
a.Add(^uint32(0)) // ^uint32(0) 等價(jià)于 ^uint32(1) + 1
fmt.Println("第三次減少后值: ", a.Load())
}
// 10
// 增加后值: 12
// 第一次減少后值: 9
// 第二次減少后值: 8
// 第三次減少后值: 7
6. Value類(lèi)型(任意類(lèi)型)
前面的操作都是針對(duì)某個(gè)類(lèi)型的操作,這個(gè)類(lèi)型可以對(duì)任意類(lèi)型操作
PS: 這種類(lèi)型,沒(méi)有Add方式
package main
import (
"fmt"
"sync/atomic"
)
type Person struct {
name string
age int8
}
func main() {
// 任意類(lèi)型
var a atomic.Value
p := Person{
name: "張三",
age: 18,
}
// 將p 結(jié)構(gòu)體存入
a.Store(p)
fmt.Printf("取出值為:%#v\n", a.Load()) // 讀取a地址的值
}
// 取出值為:main.Person{name:"張三", age:18}