go map fatal error: concurrent map iteration and map write 读写锁与深度拷贝的坑
起因
从币安实时拉取交易对的数据,这里使用了 map
,用于存放每个交易对的最新价格,由于 map
并不是并发安全的所以加了读写锁。
但系统有时候还是会发生 fatal error: concurrent map iteration and map write
错误
使用代码如下:
type BinanceSymbolPrice struct {
Item map[string]SymbolPrice
Lock sync.RWMutex
}
func NewSymbolPrice() *BinanceSymbolPrice {
info := &BinanceSymbolPrice{
Item: make(map[string]SymbolPrice),
}
return info
}
/**
* 现货最新价格行情操作
*/
func (bst *BinanceSymbolPrice) GetAllSymbolPriceInfo() map[string]SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
return bst.Item
}
func (bst *BinanceSymbolPrice) GetSymbolPriceInfo(symbol string) SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
info, exist := bst.Item[symbol]
if !exist {
return SymbolPrice{}
}
return info
}
func (bst *BinanceSymbolPrice) AddSymbolPriceItem(symbol string, SymbolPrice SymbolPrice) {
bst.Lock.Lock()
defer bst.Lock.Unlock()
if _, ok := bst.Item[symbol]; ok {
return
}
bst.Item[symbol] = SymbolPrice
}
func (bst *BinanceSymbolPrice) DelSymbolPriceInfo(symbol string) {
bst.Lock.Lock()
defer bst.Lock.Unlock()
if _, ok := bst.Item[symbol]; !ok {
return
}
delete(bst.Item, symbol)
}
// 更新交易对价格
func (bst *BinanceSymbolPrice) ResetSymbolPriceInfo(symbol string, SymbolPrice SymbolPrice) {
bst.Lock.Lock()
defer bst.Lock.Unlock()
bst.Item[symbol] = SymbolPrice
}
分析
经过自己写测试文件,才找出来问题的原因,问题代码如下
func (bst *BinanceSymbolPrice) GetAllSymbolPriceInfo() map[string]SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
return bst.Item
}
这段代码的含义是取出map中的所有数据,咋一看没什么问题,可是忽略了 map
是引用类型,这样实际上传递的是地址,还是会对源数据进行访问,从而造成了 map
读写并发。
修改如下:
func (bst *BinanceSymbolPrice) GetAllSymbolPriceInfo() map[string]SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
item := make(map[string]SymbolPrice)
for key, value := range bst.Item {
item[key] = value
}
return item
}
新初始化声明了一个新 map
,把查询出来的数据重新copy到新的map中,这样再使用时,访问到的数据就不再是同一份数据了,从而避免了 map
读写并发。
在Go语言1.9版本后提供了一种并发安全的 map
,sync.Map
推荐大家使用。
这篇文章解释了如何使用 go sync.map的使用