Golang使用Map的正确姿势

作者: Gundy_ | 来源:发表于2019-04-18 23:17 被阅读9次

    初始化后再使用

    Golang中,map是引用类型,如切片一样,通过下面的代码声明后指向的是nil,所以千万别直接声明后就使用,新手可能经常会犯如下错误:

    var m map[string]string
    m["result"] = "result"
    

    由于字典是引用类型,所以当我们仅声明而不初始化一个字典类型的变量的时候,他的值是nil。对值为nil的字段除添加键值对外其他操作都不会引发错误。上面的第一行代码对其进行写入操作,就是对空指针的引用,这将会造成一个painc。所以,得记得用 make函数对其进行分配内存和初始化:

    m := make(map[string]string)
    

    并发安全

    并发安全也叫线程安全,在并发中出现了数据的丢失,称为并发不安全我们都知道非原子操作的都不是并发安全的,在Golang中map,其读写操作并不保证并发安全。如下面的操作

    c := make(map[string]string)
    wg := sync.WaitGroup{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            k, v := strconv.Itoa(n), strconv.Itoa(n)
            c[k] = v
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println(c)
    

    运行则会出现下面的错误

    fatal error: concurrent map writes
    

    你可以通过运行go run --race来对程序进行竞态检测

    那么如何解决这个问题呢?

    通过锁机制解决并发问题

    下面介绍几种常见的并发安全的操作map的方法

    sync.Mutex

    c := make(map[string]string)
    wg := sync.WaitGroup{}
    var lock sync.Mutex
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            k, v := strconv.Itoa(n), strconv.Itoa(n)
            lock.Lock()
            c[k] = v
            lock.Unlock()
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println(c)
    

    当然这里也可以使用读写锁sync.RWMutex,这样并发读多的场景下性能要好。

    sync.Map

    sync.Map 是官方出品的并发安全的map,他在内部使用了大量的原子操作来存取键和值,并使用了read和dirty二个原生map作为存储介质,具体实现流程可阅读相关源码。

    var m sync.Map
    
    //写
    m.Store("foo", "hello world")
    m.Store("slice", []int{1, 2, 3, 4, 5, 6, 7})
    m.Store("int", 123)
    //读
    m.Load("foo")
    m.Load("slice")
    m.Load("int")
    //删
    m.Delete("int")
    

    第三方map包

    第三方包的实现都大同小异,基本上都是使用分离锁来实现并发安全的,具体分离锁来实现并发安全的原理可参考下面的延伸阅读

    concurrent-map

    m := cmap.New()
    //写
    m.Set("foo", "hello world")
    m.Set("slice", []int{1, 2, 3, 4, 5, 6, 7})
    m.Set("int", 1)
    //读
    m.Get("foo")  
    m.Get("slice") 
    m.Get("int")  
    

    go-concurrentMap

    m := concurrent.NewConcurrentMap()
    m.Put("foo", "hello world")
    m.Put("slice", []int{1, 2, 3, 4, 5, 6, 7})
    m.Put("int", 1)
    //读
    m.Get("foo")  
    m.Get("slice") 
    m.Get("int") 
    

    延伸阅读

    HashMap原理和实现

    探索 ConcurrentHashMap 高并发性的实现机制

    本文亦在微信公众号【小道资讯】发布,欢迎扫码关注!


    image

    相关文章

      网友评论

        本文标题:Golang使用Map的正确姿势

        本文链接:https://www.haomeiwen.com/subject/sdtfgqtx.html