原子操作

原子操作即执行过程不能被中断的操作。在针对某个值的原子操作执行过程当值,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。

参考

jefffff

Stay hungry. Stay Foolish COOL

Go backend developer

China Amoy