go在1.6的版本中加入了 Detection of unsafe concurrent access to maps
,
考虑下面的代码:
1 | const workers = 100 // what if we have 1, 2, 25? |
在运行时会有如下的输出:
1 | Outputs: |
更多详情请参考: https://talks.golang.org/2016/state-of-go.slide#30
而我们项目使用gorequest
, 模拟一下我们的使用场景:
1 | package extension |
调用过程:
1 | package main |
在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的结构上来分析:
1 | // A SuperAgent is a object storing all request data for client. |
注意到Data这个属性, 是一个 map,
1 | func (s *SuperAgent) SendString(content string) *SuperAgent { |
当我们使用goroutine去调用时,由于是同一个SuperAgent的实例,在给Data赋值的过程中s.Data[k] = v
, 就会引起concurrent map read and map write
的问题。
官方给出的解决方案是在赋值时加锁:
1 | func count(n int) { |
而且从官方给出的Benchmark results来看, 并不会出现很大的性能损耗。
详情请参考: https://talks.golang.org/2016/state-of-go.slide#31
我们出现concurrent write的原因在于用于同一个SuperAgent实例, 如果我们不将Request作为SuperAgent的单例, 而是每次去new一个,那么就不会存在这个问题.
1 | //增加一个获取*gorequest.SuperAgent实例的方法,而不让它变成一个单例 |