atomic
atomic 操作的对象是一个地址,你需要把可寻址的变量的地址作为参数传递给方法,而不是把变量的值传递给方法。
Add

Add 方法就是给第一个参数地址中的值增加一个 delta 值。对于有符号的整数来说,delta 可以是一个负数,相当于减去一个值。
对于无符号的整数和 uinptr 类型来说,怎么实现减去一个值呢?毕竟,atomic 并没有提供单独的减法操作。你可以利用计算机补码的规则,把减法变成加法。以 uint32 类型为例:
1 2
| AddUint32(&x, ^uint32(c-1))
|
如果是对 uint64 的值进行操作,那么,就把上面的代码中的 uint32 替换成 uint64。尤其是减 1 这种特殊的操作,我们可以简化为:
1 2
| AddUint32(&x, ^uint32(0))
|
CAS

在 CAS 的方法签名中,需要提供要操作的地址、原数据值、新值,如下所示:
1 2
| func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
|
这个方法会比较当前 addr 地址里的值是不是 old,如果不等于 old,就返回 false;如果等于 old,就把此地址的值替换成 new 值,返回 true。这就相当于“判断相等才替换”。
Swap
如果不需要比较旧值,只是比较粗暴地替换的话,就可以使用 Swap 方法,它替换后还可以返回旧值

Load
Load 方法会取出 addr 地址中的值,即使在多处理器、多核、有 CPU cache 的情况下,这个操作也能保证 Load 是一个原子操作。

Store
Store 方法会把一个值存入到指定的 addr 地址中,即使在多处理器、多核、有 CPU cache 的情况下,这个操作也能保证 Store 是一个原子操作。别的 goroutine 通过 Load 读取出来,不会看到存取了一半的值。

Value
刚刚说的都是一些比较常见的类型,其实,atomic 还提供了一个特殊的类型:Value。它可以原子地存取对象类型,但也只能存取,不能 CAS 和 Swap,目前已经有Swap,CompareAndSwap 方法,常常用在配置变更等场景中。

启动一个 goroutine,然后让它随机 sleep 一段时间,之后就变更一下配置,并通过我们前面学到的 Cond 并发原语,通知其它的 reader 去加载新的配置。接下来,我们启动一个 goroutine 等待配置变更的信号,一旦有变更,它就会加载最新的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| type Config struct { NodeName string Addr string Count int32 }
func loadNewConfig() Config { return Config{ NodeName: "北京", Addr: "10.77.95.27", Count: rand.Int31(), } } func main() { var config atomic.Value config.Store(loadNewConfig()) var cond = sync.NewCond(&sync.Mutex{})
go func() { for { time.Sleep(time.Duration(5+rand.Int63n(5)) * time.Second) config.Store(loadNewConfig()) cond.Broadcast() } }()
go func() { for { cond.L.Lock() cond.Wait() c := config.Load().(Config) fmt.Printf("new config: %+v\n", c) cond.L.Unlock() } }()
select {} }
|
第三方库的扩展
关注度比较高的 uber-go/atomic,它定义和封装了几种与常见类型相对应的原子操作类型,这些类型提供了原子操作的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "github.com/uber-go/atomic" )
func main() { var atom atomic.Uint32 atom.Store(42) fmt.Println(atom)
atom.Sub(2) fmt.Println(atom)
atom.Swap(1) fmt.Println(atom) atom.CAS(1, 11) fmt.Println(atom) }
|
总结

示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| package go_test
import ( "fmt" "sync/atomic" "testing" )
func TestAtomicAdd(t *testing.T) { var numInt32 int32 atomic.AddInt32(&numInt32, 1) fmt.Println(numInt32)
var numInt64 int64 atomic.AddInt64(&numInt64, 1) fmt.Println(numInt64)
var numUint32 uint32 atomic.AddUint32(&numUint32, 1) fmt.Println(numUint32)
var numUint64 uint64 atomic.AddUint64(&numUint64, 1) fmt.Println(numUint64)
var numUintPtr uintptr atomic.AddUintptr(&numUintPtr, 1) fmt.Println(numUintPtr)
atomic.AddUint32(&numUint32, ^uint32(numUint32-1)) print(numUint32) }
func TestAtomicCAS(t *testing.T) { var numInt32 int32 = 111 atomic.CompareAndSwapInt32(&numInt32, 111, 222) fmt.Println(numInt32)
atomic.CompareAndSwapInt32(&numInt32, 222, 333) fmt.Println(numInt32) }
func TestAtomicSwap(t *testing.T) { var numInt32 int32 = 111 atomic.SwapInt32(&numInt32, 222) fmt.Println(numInt32)
atomic.SwapInt32(&numInt32, 333) fmt.Println(numInt32)
}
func TestAtomicLoad(t *testing.T) { var numInt32 int32 = 111 atomic.LoadInt32(&numInt32) fmt.Println(numInt32) }
func TestAtomicStore(t *testing.T) { var numInt32 int32 atomic.StoreInt32(&numInt32, 111) fmt.Println(numInt32) }
func TestAtomicValue(t *testing.T) { value := atomic.Value{} value.Store("test value") fmt.Println(value.Load()) }
|