go在1.6的版本中加入了 Detection of unsafe concurrent access to maps,
考虑下面的代码:
const workers = 100 // what if we have 1, 2, 25?
var wg sync.WaitGroup
wg.Add(workers)
m := map[int]int{}
for i := 1; i <= workers; i++ {
go func(i int) {
for j := 0; j < i; j++ {
m[i]++
}
wg.Done()
}(i)
}
wg.Wait()
// A SuperAgent is a object storing all request data for client.
type SuperAgent struct {
Url string
Method string
Header map[string]string
TargetType string
ForceType string
Data map[string]interface{}
FormData url.Values
QueryData url.Values
Client *http.Client
Transport *http.Transport
Cookies []*http.Cookie
Errors []error
BasicAuth struct{ Username, Password string }
Debug bool
logger *log.Logger
}
注意到_Data_这个属性, 是一个 map,
func (s *SuperAgent) SendString(content string) *SuperAgent {
var val map[string]interface{}
// check if it is json format
d := json.NewDecoder(strings.NewReader(content))
d.UseNumber()
if err := d.Decode(&val); err == nil {
for k, v := range val {
s.Data[k] = v
}
} else {
...
}
}
当我们使用goroutine去调用时,由于是同一个SuperAgent的实例,在给Data赋值的过程中s.Data[k] = v, 就会引起concurrent map read and map write的问题。
官方给出的解决方案是在赋值时加锁:
func count(n int) {
var wg sync.WaitGroup
wg.Add(n)
m := map[int]int{}
var mu sync.Mutex
for i := 1; i <= n; i++ {
go func(i int) {
for j := 0; j < i; j++ {
mu.Lock()
m[i]++
mu.Unlock()
}
wg.Done()
}(i)
}
wg.Wait()
}
golang升级到1.7过程中使用gorequest碰到的问题
go在1.6的版本中加入了
Detection of unsafe concurrent access to maps
, 考虑下面的代码:在运行时会有如下的输出:
更多详情请参考: https://talks.golang.org/2016/state-of-go.slide#30
而我们项目使用
gorequest
, 模拟一下我们的使用场景:调用过程:
在1.5版本中,有时候会出现
panic: runtime error: invalid memory address or nil pointer dereference
, 因为在跑test case过程中,有时候能够happy pass, 而且服务一直也没有出什么问题,便一直没有解决.今天试图将go升级到1.7, 发现了
fatal error: concurrent map read and map write
, 而且频率很高,便想着找到原因,到底是在哪一步出错的。通过调试,最终定位定位到了gorequest的
SendString
方法中.首先从SuperAgent的结构上来分析:
注意到_Data_这个属性, 是一个 map,
当我们使用goroutine去调用时,由于是同一个SuperAgent的实例,在给Data赋值的过程中
s.Data[k] = v
, 就会引起concurrent map read and map write
的问题。官方给出的解决方案是在赋值时加锁:
而且从官方给出的Benchmark results来看, 并不会出现很大的性能损耗。 详情请参考: https://talks.golang.org/2016/state-of-go.slide#31
我们出现concurrent write的原因在于用于同一个SuperAgent实例, 如果我们不将Request作为SuperAgent的单例, 而是每次去new一个,那么就不会存在这个问题.