• 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版本后提供了一种并发安全的 mapsync.Map 推荐大家使用。

    一.问题

    map是线程不安全的,即使并发读写没有冲突也会报错(fatal error: concurrent map read and map write):

    1.  
      func main() {
    2.  
      m := make(map[int]int)
    3.  
      go func() {
    4.  
      for {
    5.  
      _ = m[1]
    6.  
      }
    7.  
      }()
    8.  
      go func() {
    9.  
      for {
    10.  
      m[2] = 2
    11.  
      }
    12.  
      }()
    13.  
      time.Sleep(time.Second * 1<em>)
    14.  
      }</em>

    报错原因:


    写的时候设置了h.flags,获取的时候检查报错

    写完会清除标志位

    二.解决办法

    1)使用读写锁sync.RWMutex:

    1.  
      var counter = struct{
    2.  
      sync.RWMutex
    3.  
      m map[string]int
    4.  
      }{m: make(map[string]int)}
    
    
    1.  
      counter.RLock()
    2.  
      n := counter.m["some_key"]
    3.  
      counter.RUnlock()
    4.  
      fmt.Println("some_key:", n)
    1.  
      counter.Lock()
    2.  
      counter.m["some_key"]++
    3.  
      counter.Unlock()



    2)使用类似java的Concurrenthashmap(https://github.com/orcaman/concurrent-map)

    3)使用sync.Map

    1.  
      func main() {
    2.  
      m := sync.Map{}
    3.  
      m.Store(1, 1)
    4.  
      m.Store("1", "1")
    5.  
      fmt.Println(m.Load("1"))
    6.  
      m.Delete("1")
    7.  
      fmt.Println(m.Load("1"))
    8.  
      }


    补充:对于不容易发现的并发问题,可以使用-race参数进行并发检测

    1.  
      func main() {
    2.  
      a := 1
    3.  
      go func() {
    4.  
      a = 2
    5.  
      }()
    6.  
      a = 3
    7.  
      fmt.Println(a)
    8.  
       
    9.  
      time.Sleep(time.Second * 1)
    10.  
      }



  • 相关阅读:
    GreenPlum 锁表以及解除锁定
    Postgresql 解决锁表
    Greenplum 查看连接与锁信息数据字典
    Greenplum 常用数据字典
    Linux 内核参数说明
    Greenplum 如何直连segment节点
    GreenPlum 数据备份与恢复
    unity 获取DontDestroyOnLoad的游戏对象
    scheduleOnce时出错,CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: 0 to 0"
    正比适配,留黑边
  • 原文地址:https://www.cnblogs.com/ExMan/p/16602023.html
Copyright © 2020-2023  润新知