引言
在goroutine中访问外部的变量并不安全,我们先看看下面这个例子,我们执行一次计数,使用sync.WaitGroup
包保证我们创建的1000个goroutine全部执行完毕后再输出n
,运行程序看看结果如何:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var n int32
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
n = n + 1
wg.Done()
}()
}
wg.Wait()
fmt.Println(n) // 每次执行的结果不是1000,小于等于一千数值波动
}·
电脑多核的情况下,不限制cpu执行上面代码,每次执行的结果不是1000,而是小于等于一千数值波动。为什么会出现这种情况呢?上面我们通过for循环里创建1000个goroutine,每个goroutine都将n加上1,加入有其中两个goroutine如下执行步骤:
- goroutine1读取变量n 值为800
- goroutine2读取变量n 值为800
- goroutine1执行n+1,n变为801
- goroutine2执行n+1,n变为801
- 两次goroutine执行完毕
- 结果n少加了一次1,所以最终的结果就比预期少1
使用sync/atomic修改上述代码
我们使用sync/atomic原子操作解决上述并发安全问题,代码如下:
func main() {
var n int32
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
atomic.AddInt32(&n, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println(atomic.LoadInt32(&n)) // 1000
}·
我们使用 atomic.AddInt32
进行加操作,最终输出预期的结果, 其实``atomic`包中的方法在执行完毕之前不会被其他的任务或者事件中断,该操作是并发安全,在向此地址写入或者读取值时原子性的操作,如果其他goroutine尝试读取或写入会被阻塞,知道此次原子操作完成。
在sync/atomic中提供了许多原子操作支持,主要有五大类:
- Load:该类方法主要负责从相应的内存地址中获取对应的值
- Store:该类主要负责将对应的值保存在相应的内存地址中
- Add:该类方法可以理解是Load和Store的结合,也就是先Load然后Add
- Swap:该类方法可以理解为先Load,在Store新值,然后返回旧值
- CompareAndSwap:该类方法可以这样理解:先比较旧数据和地址中保存数据的值,如果相同的话,执行Swap,把新的数值保存在地址中,返回true,如果不同,那么直接返回false
常用方法
sync/atomic
包中的方法都如下格式:
func AddT(addr *T, delta T)(new T)
func LoadT(addr *T) (val T)
func StoreT(addr *T, val T)
func SwapT(addr *T, new T) (old T)
func CompareAndSwapT(addr *T, old, new T) (swapped bool)
例如:
// 下面是in32的
func AddInt32(addr *int32, delta int32)(new int32)
func LoadInt32(addr *int32) (val int32)
func StoreInt32(addr *int32, val int32)
func SwapInt32(addr *int32, new int32) (old int32)
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
// 为(安全)指针类型提供以下四个原子函数:
// 并没有AddPointer函数,因为go的指针不支持算术运算
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func SwapPointer(addr *unsafe.Pointer, new T) (old unsafe.Pointer)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)