• golang map是线程安全的吗


    不是线程安全的。在同一时间段内,让不同 goroutine 中的代码,对同一个字典进行读写操作是不安全
    的。字典值本身可能会因这些操作而产生混乱,相关的程序也可能会因此发生不可预知的问题。

    1.什么是map?

    map是一个可以存储key/value对的一种数据结构,map像slice一样是引用类型,map内部实现是一个hash table,因此在map中存入的数据是无序的(map内部实现)。而每次从map中读取的数据也是无序的,因为golang在设计之初,map迭代器的顺序就是随机的,有别于C/C++,虽然存入map的数据是无序的,但是每次从map中读取的数据是一样的

    声明和初始化

    // 声明一个map,因为map是引用类型,所以m是nil
    var m map[KeyType]ValueType
    // 初始化方式一,空map,空并不是nil
    m := map[KeyType]ValueType{}
    //初始化方式二,两种初始化的方式是等价的
    m := make(map[KeyType]ValueType)

    基本操作

    m := map[string]int{}
    // 增加一个key/value对
    m["Tony"] = 10
    // 删除Key Tony
    delete(m, "Tony")
    // 修改Key Tony的值
    m["Tony"] = 20
    // 判断某个Key是否存在
    if _, ok := m["Tony"]; ok {
        fmt.Println("Tony is exists")
    }
    // 遍历map
    for key, value := range m {
        fmt.Printf("Key = %s, Value = %d", key, value)
    }
    // 使用多个值对map进行初始化
    mp := map[string]int {
        "Tina": 10,
        "Divad": 20,
        "Tom": 5,
    }

    Key和Value可以使用什么类型?

    Key :只要是可比较(可以使用==进行比较,两边的操作数可以相互赋值)的类型就可以,像整形,字符串类型,浮点型,数组(必须类型相同);而map,slice和function不能作为Key的类型。

    Value :任何类型都可以。

    2.如何安全的使用map

    方式一:sync.Map

    在 2017 年发布的 Go 1.9 中正式加入了并发安全的字典类型sync.Map。这个字典类型提供了一些常用的键值存取操作方法,并保证了这些操作的并发安全。同时,它的存、取、删等操作都可以基本保证在常数时间内执行完毕。换句话说,它们的算法复杂度与map类型一样都是O(1)的。在有些时候,与单纯使用原生map和互斥锁的方案相比,使用sync.Map可以显著地减少锁的争用。sync.Map本身虽然也用到了锁,但是,它其实在尽可能地避免使用锁。

    代码:

     var ma sync.Map// 该类型是开箱即用,只需要声明既可
        ma.Store("key", "value") // 存储值
        ma.Delete("key") //删除值
        ma.LoadOrStore("key", "value")// 获取值,如果没有则存储
        fmt.Println(ma.Load("key"))//获取值
        
        //遍历
        ma.Range(func(key, value interface{}) bool {
            fmt.Printf("key:%s ,value:%s 
    ", key, value)
            //如果返回:false,则退出循环,
            return true
        })

    方式二:增加同步机制

    map在并发访问中使用不安全,因为不清楚当同时对map进行读写的时候会发生什么,如果像通过goroutine进行并发访问,则需要一种同步机制来保证访问数据的安全性。一种方式是使用sync.RWMutex

    // 通过匿名结构体声明了一个变量counter,变量中包含了map和sync.RWMutex
    var counter = struct{
        sync.RWMutex
        m map[string]int
    }{m: make(map[string]int)}
    // 读取数据的时候使用读锁
    counter.RLock()
    n := counter.m["Tony"]
    counter.RUnlock()
    // 写数据的使用使用写锁
    counter.Lock()
    counter.m["Tony"]++
    counter.Unlock()

    扩展:

    map映射过程

    哈希表中查找与某个键值对应的那个元素值,那么我们需要先把键值作为参数传给这个哈希表。哈希表会先用哈希函数(hash function)把键值转换为哈希值。哈希值通常是一个无符号的整数。一个哈希表会持有一定
    数量的桶(bucket),也可称之为哈希桶,这些哈希桶会均匀地储存其所属哈希表收纳的那些键 - 元素对。因此,哈希表会先用这个键的哈希值的低几位去定位到一个哈希桶,然后再去这个哈希桶中,查找这个键。
    由于键 - 元素对总是被捆绑在一起存储的,所以一旦找到了键,就一定能找到对应的元素值。随后,哈希表就会把相应的元素值作为结果返回。只要这个键 - 元素对存在于哈希表中就一定会被查找到。

    为什么说并发安全字典在尽量避免使用锁?

    //sync.Map 包的结构
    type Map struct {
       mu Mutex //
       
       /*
            由read字段的类型可知,sync.Map在替换只读字典的时候根本用不着锁。另外,这个只读字典
        在存储键值对的时候,还在值之上封装了一层。它先把值转换为了unsafe.Pointer类型的值,然后再把后者封装,并储存在其中的原生字典中。如此一来,在变更某个键所对应的值的时候,就也可以使用原子操作了。
       */
       read atomic.Value// 只读字典
       /*
            它存储键值对的方式与read字段中的原生字典一致,它的键类型也是interface{},并且同样是把值先做   转换和封装后再进行储存的
       */
       dirty map[interface{}]*entry//脏字典。
       misses int//重建的判断条件
    }

    查找键值对的时候,会先去只读字典中寻找,并不需要锁定互斥锁。只有当确定“只读字典中没有,但脏字典中可能会有这个键”的时候,它才会在锁的保护下去访问脏字典。相对应的,sync.Map在存储键值对的时候,只要只读字典中已存有这个键,并且该键值对未被标记为“已删除”,就会把新值存到里面并直接返回,这种情况下也不需要用到锁。否则,它才会在锁的保护下把键值对存储到脏字典中。这个时候,该键值对的“已删除”标记会被
    抹去。

    当一个键值对应该被删除,但却仍然存在于只读字典中的时候,才会被用标记为“已删除”的方式进行逻辑删除,而不会直接被物理删除。

    这种情况会在重建脏字典以后的一段时间内出现。不过,过不了多久,它们就会被真正删除掉。
    在查找和遍历键值对的时候,已被逻辑删除的键值对永远会被无视。

    对于删除键值对,sync.Map会先去检查只读字典中是否有对应的键。如果没有,脏字典中可能有,那么它就会在锁的保护下,试图从脏字典中删掉该键值对。最后,sync.Map会把该键值对中指向值的那个指针置为nil,这是另一种逻辑删除的方式。

    除此之外,还有一个细节需要注意,只读字典和脏字典之间是会互相转换的。在脏字典中查找键
    值对次数足够多的时候,sync.Map会把脏字典直接作为只读字典,保存在它的read字段中,然
    后把代表脏字典的dirty字段的值置为nil。

    在读操作有很多但写操作却很少的情况下,并发安全字典的性能往往会更好。在几个写操作当中,新增键值对的操作对并发安全字典的性能影响是最大的,其次是删除操作,最后才是修改操作。

    如果被操作的键值对已经存在于sync.Map的只读字典中,并且没有被逻辑删除,那么修改它并
    不会使用到锁,对其性能的影响就会很小。

     

  • 相关阅读:
    java基础,集合,ConcurrentHashMap,JDK1.7理解
    单例的几种方式,以及如何破坏单例,使用枚举保护单例;
    测试一软件测试基础知识总结
    spring cloud gateway(三、实现限流)
    gateway(二、过滤器)
    spring cloud gateway
    spring cloud zuul网关
    进入mysql方法
    spring cloud consul 服务治理
    多模块项目之坑
  • 原文地址:https://www.cnblogs.com/peteremperor/p/14469710.html
Copyright © 2020-2023  润新知