func AtomicAdd() {
var a int32 = 0
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 1000000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt32(&a, 1)
}()
}
wg.Wait()
timeSpends := time.Now().Sub(start).Nanoseconds()
fmt.Printf("use atomic a is %d, spend time: %v\n", atomic.LoadInt32(&a), timeSpends)
}
啥是原子操作呢?顾名思义,原子操作就是具备原子性的操作... 是不是感觉说了跟没说一样,原子性的解释如下:
Go 语言提供了哪些原子操作
Go
语言通过内置包sync/atomic
提供了对原子操作的支持,其提供的原子操作有以下几大类:AddXXXType
LoadXXXType
Store
开头CompareAndSwap
开通,也就是CAS
,像Go
的很多并发原语实现就是依赖的CAS
操作Store
开头的操作方法,这个简单粗暴一些,不比较直接交换,一般不怎么用这个操作互斥锁跟原子操作的区别
Mutex
由操作系统的调度器实现,而atomic
包中的原子操作则由底层硬件指令直接提供支持,这些指令在执行的过程中是不允许中断的,因此原子操作可以在lock-free
的情况下保证并发安全,并且它的性能也能做到随CPU
个数的增多而线性扩展。原子操作增加、载入
完整源码,请访问atomic示例一
需要注意的是,所有原子操作方法的被操作数形参必须是指针类型,通过指针变量可以获取被操作数在内存中的地址,从而施加特殊的CPU指令,确保同一时间只有一个goroutine能够进行操作。
比较并交换
该操作简称
CAS
(Compare And Swap)。 这类操作的前缀为CompareAndSwap
:该操作在进行交换前首先确保被操作数的值未被更改,即仍然保存着参数
old
所记录的值,满足此前提条件下才进行交换操作。CAS
的做法类似操作数据库时常见的乐观锁机制。atomic.Value保证任意值的读写安全
atomic
包里提供了一套Store
开头的方法,用来保证各种类型变量的并发写安全,避免其他操作读到了修改变量过程中的脏数据。这些操作方法的定义与上面介绍的那些操作的方法类似,就不再演示怎么使用这些方法了。值得一提的是如果你想要并发安全的设置一个结构体的多个字段,除了把结构体转换为指针,通过
StorePointer
设置外,还可以使用atomic
包后来引入的atomic.Value
,它在底层为我们完成了从具体指针类型到unsafe.Pointer
之间的转换。有了
atomic.Value
后,它使得我们可以不依赖于不保证兼容性的unsafe.Pointer
类型,同时又能将任意数据类型的读写操作封装成原子性操作(中间状态对外不可见)。atomic.Value
类型对外暴露了两个方法:v.Store(c)
- 写操作,将原始的变量c
存放到一个atomic.Value
类型的v
里。c := v.Load()
- 读操作,从线程安全的v
中读取上一步存放的内容。1.17 版本我看还增加了
Swap
和CompareAndSwap
方法。下面是一个简单的例子演示
atomic.Value
的用法。完整源代码请访问:atomic示例二
你也可以试试,不用
atomic.Value
,直接给Rectange
类型的指针变量赋值,看看在并发条件下,两个字段的值是不是能跟预期的一样变成10和15。总结
原子操作由底层硬件支持,而锁则由操作系统的调度器实现。锁应当用来保护一段逻辑,对于一个变量更新的保护,原子操作通常会更有效率,并且更能利用计算机多核的优势,如果要更新的是一个复合对象,则应当使用
atomic.Value
封装好的实现。