1.data race介绍
Go(从v1.1开始)具有内置的数据竞争检测器,data race是两个或多个goroutine访问同一个资源(如变量或数据结构),并尝试对该资源进行读写而不考虑其他goroutine。
1.1运行时检查竟态的命令:go run -race main.go
1.2构建时检查竟态的命令:go build -race main.go
1.3测试时检查竟态的命令:go test -race main.go
race信息如下,能详细看出是代码的哪一行
2.原子赋值问题
2.1如下程序,存在race
2.2i++是原子赋值吗?否
但是我们通过查看底层汇编:
实际上是有三行汇编代码在执行增加Counter。这三行汇编很可能存在上下文切换,从技术上讲,是并发不安全的。
2.3struct赋值是原子赋值吗?否
上面程序可能会出现:Ben says,hello my name is Jerry,为什么呢???
interface内部实际是两个machine word值,如下结构
当赋值语句var maker IceCreamMaker = ben执行时,编译器的操作是
在上面例子中,Ben和Jerry的结构是相同的,因此某种意义上是兼容的。如果两个结构体字段不同呢?
那么可能会panic,因为结构不同,赋值会出问题。
如果是一个普通的指针、map、slice可以安全的更新吗?
不可以,没有安全的赋值,需要借助锁或automic 等实现全局变量的赋值。
3.automic
3.1automic样例
读多写少使用读写锁
读特别多,可以尝试使用automic.value
看下面样例
config_test.go
package config
import (
"sync"
"sync/atomic"
"testing"
)
type Config struct {
a []int
}
func (c *Config)T() {
}
//automic处理
func BenchmarkAtomatic(b *testing.B) {
var v atomic.Value
v.Store(&Config{})
go func() {
i :=0
for{
i++
cfg := &Config{a:[]int{i,i+1,i+2,i+3,i+4,i+5}}
v.Store(cfg)
}
}()
var wg sync.WaitGroup
for n:=0;n<4;n++{
wg.Add(1)
go func() {
for n:=0;n<b.N;n++{
cfg := v.Load().(*Config)
cfg.T()
//fmt.Printf("%v\n",cfg)
}
wg.Done()
}()
}
wg.Wait()
}
//读写锁
func BenchmarkMutex(b *testing.B) {
var l sync.RWMutex
var cfg *Config
go func() {
i :=0
for{
i++
l.Lock()
cfg = &Config{a:[]int{i,i+1,i+2,i+3,i+4,i+5}}
l.Unlock()
}
}()
var wg sync.WaitGroup
for n:=0;n<4;n++{
wg.Add(1)
go func() {
for n:=0;n<b.N;n++{
l.RLock()
cfg.T()
l.RUnlock()
//fmt.Printf("%v\n",cfg)
}
wg.Done()
}()
}
wg.Wait()
}
automic耗时更少
3.2automic copy on write
Opy-On-Write思路在微服务降级或local cache场景中经常使用。写时复制指的是:写操作的时候复制全量老数据到一个新的对象中,携带上本次新写的数据,之后利用原子替换(automic.Value)更新调用者的变量,来完成无锁访问共享数据