原子操作
原子操作即执行过程不能被中断的操作。在针对某个值的原子操作执行过程当值,cpu绝不会再去执行其他针对该值的操作,无论这些其他操作是否为原子操作。
go-atomic
查看Mutex、RWMutex的源码,底层是通过atomic包中的一些原子操作来实现。
Go标准库 sync/atomic 提供了对基础类型 int32、int64、uint32、uint64、uintptr、Pointer(Add 方法不支持) 的原子级内存操作。其中包括:
- Add (给第一个参数地址中的值增加一个 delta 值)
- CompareAndSwap(判断相等即替换))
- Swap(不需要比较旧值,直接替换,返回旧值)
- Load(方法会取出 addr 地址中的值)
- Store( 方法会把一个值存入到指定的 addr 地址中)
我们通过一个例子可以快速了解atomic封装的这些api
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var x int32 = 0
//func AddInt32(addr *int32, delta int32) (new int32)
y := atomic.AddInt32(&x, int32(1))
fmt.Printf("x=%d,y=%d \n", x, y)
//OutPut: x=1,y=1
// func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
// addr 地址里的值是不是 old,如果不等于 old,就返回 false;如果等于 old,就把此地址的值替换成 new 值,返回 true
isCompare := atomic.CompareAndSwapInt32(&x, int32(0), int32(2))
fmt.Printf("isCompare=%v,x=%d \n", isCompare, x)
//OutPut: isCompare=false,x=1
//不相等,故x还是1
isCompare2 := atomic.CompareAndSwapInt32(&x, int32(1), int32(2))
fmt.Printf("isCompare=%v,x=%d \n", isCompare2, x)
//OutPut: isCompare=true,x=2
//相等,故x还是2
//func SwapInt32(addr *int32, new int32) (old int32)
xOld := atomic.SwapInt32(&x, int32(3))
fmt.Printf("xOld=%d,x=%d \n", xOld, x)
//OutPut: xOld=2,x=3
//不比较,故x替换3
//func LoadInt32(addr *int32) (val int32)
vValue := atomic.LoadInt32(&x)
fmt.Printf("vValue=%d \n", vValue)
//OutPut: xOld=2,x=3
//获取x的值3
//func StoreInt32(addr *int32, val int32)
atomic.StoreInt32(&x, 8)
vValue2 := atomic.LoadInt32(&x)
fmt.Printf("vValue2=%d \n", vValue2)
//OutPut:vValue2=8
}
小试牛刀
atomic 比较常见的类型 还提供了一个特殊的类型:Value,但是只支持 load、store。 这里模拟一个场景:当配置变更后,期待其他goroutine可以收到通知和变更。
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
type Config struct {
Network string
Addr string
ReadTimeout int32
WriteTimeout int32
}
func loadConfig() Config {
return Config{
Network: "redis",
Addr: "127.0.0.1:6379",
ReadTimeout: 60,
WriteTimeout: 60,
}
}
var (
done bool
config atomic.Value
)
func main() {
config.Store(loadConfig())
var cond = sync.NewCond(&sync.Mutex{})
go waitForLoad(1, cond)
go waitForLoad(2, cond)
go beginLoad(cond)
select {}
}
func beginLoad(cond *sync.Cond) {
for {
time.Sleep(3 * time.Second)
config.Store(loadConfig())
cond.Broadcast()
}
}
func waitForLoad(node int, cond *sync.Cond) {
cond.L.Lock()
for {
if !done {
cond.Wait()
}
c := config.Load().(Config)
fmt.Printf("node: %d - redis config: %+v\n", node, c)
}
cond.L.Unlock()
}
OutPut:
node: 2 - redis config: {Network:redis Addr:127.0.0.1:6379 ReadTimeout:60 WriteTimeout:60}
node: 1 - redis config: {Network:redis Addr:127.0.0.1:6379 ReadTimeout:60 WriteTimeout:60}
uber-go/atomic
uber-go/atomic对标准库进行进一步封装,采用面向对象的使用方式。 这些类型包括 Bool、Duration、Error、Float64、Int32、Int64、String、Uint32、Uint64 等 举个例子uint32的减法你可能是这么写的
atomic.AddUint32(&x, ^(delta - 1))
利用计算机补码的规则,把减法变成加法 。uber-go对它进行了封装
var atom atomic.Uint32
atom.Store(10)
atom.Sub(2)
atom.CAS(20, 1)
小结
本文简要介绍原子操作和go-atomic,使用场景,以及第三库uber-go/atomic。