import (
"sync"
"sync/atomic"
"testing"
)
func TestAtomic(t *testing.T) {
/*
go语言提供的原子操作都是非侵入式的,它们由标准库代码包sync/atomic中的众多函数代表。
我们调用sync/atomic中的几个函数可以对几种简单的类型进行原子操作。
这些类型包括int32,int64,uint32,uint64,uintptr,unsafe.Pointer,共6个。
这些函数的原子操作共有5种:增或减,比较并交换、载入、存储和交换它们提供了不同的功能,
切使用的场景也有区别。
*/
//增减操作
var a int32
t.Log("a:", a)
//函数名以add开头,参数一为指针,参数二与参数一类型相同
//增操作
atomic.AddInt32(&a, 3)
t.Log("a:", a)
//减操作
atomic.AddInt32(&a, -4)
t.Log("a:", a)
/*
比较并交换----Compare And Swap 简称CAS
他是假设被操作的值未曾被改变(即与旧值相等),并一旦确定这个假设的真实性就立即进行值替换
如果想安全的并发一些类型的值,我们总是应该优先使用CAS
*/
var b int32
t.Log("b:", b)
//函数会判断参数一的值是否与参数二相等,如果相等用参数三的值替换参数一的值。
atomic.CompareAndSwapInt32(&b, 0, 3)
t.Log("b:", b)
/*
载入
如果一个写操作未完成,有一个读操作就已经发生了,这样读操作使很糟糕的。
为了原子的读取某个值sync/atomic代码包同样为我们提供了一系列的函数。
这些函数都以"Load"为前缀,意为载入。
*/
var c int32
wg := sync.WaitGroup{}
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
temp := atomic.LoadInt32(&c)
if !atomic.CompareAndSwapInt32(&c, temp, (temp + 1)) {
t.Log("error")
}
}()
}
wg.Wait()
//值可能不等于50,在频繁修改的情况下CAS操作可能失败
t.Log("c:", c)
/*
存储
与读操作对应的是写入操作,sync/atomic也提供了与原子的值载入函数相对应的原子的值存储函数。
这些函数的名称均以“Store”为前缀。在原子的存储某个值的过程中,
任何cpu都不会进行针对进行同一个值的读或写操作
如果我们把所有针对此值的写操作都改为原子操作,
那么就不会出现针对此值的读操作读操作因被并发的进行而读到修改了一半的情况。
原子操作总会成功,因为他不必关心被操作值的旧值是什么。
*/
var d int32
t.Log("d:", d)
//存储操作总会成功,它不关心旧值是什么,与CAS不同
//参数一被操作的指针,参数二新的值
atomic.StoreInt32(&d, 555)
t.Log("d:", d)
/*
交换
原子交换操作,这类函数的名称都以“Swap”为前缀。
与CAS不同,交换操作直接赋予新值,不管旧值。
会返回旧值
*/
var e int32
wg2 := sync.WaitGroup{}
for i := 0; i < 20; i++ {
wg2.Add(1)
go func() {
defer wg2.Done()
temp := atomic.LoadInt32(&e)
//直接设置新值,返回旧值,与CAS不同,它不关心旧值。
old := atomic.SwapInt32(&e, (temp - 1))
t.Log("old:", old)
t.Log("e:", e)
}()
}
wg2.Wait()
t.Log("e:", e)
}