golang中线程安全的map

golang中线程安全的两种数据结构:

channel

sync包里的数据类型

atomic包里的数据类型

保证线程安全可以使用sync包下面的

互斥锁sync.Mutex

读写锁sync.RWMutex

原子操作sync.atomic

map的底层原理

count:key的数量

flags:是否进行写操作

B:桶的数量,它是以2为底的对数,即2的B次方

hash0:哈希因子

buckets:桶的列表,一个bmap数组存储键值对(当一个桶存不下时,会指向一个溢出桶存储,当这个溢出桶存不下时,会指向下一个溢出桶存储)

bmap里面包括tophash(高8位)、keys(键)(哈希运算+取模运算得到位置)、elems(值)、overflow(溢出桶)

map不可寻址

map的扩容

等量扩容:当有大量的key被删除时,为了提高查找效率,过滤掉无用的溢出桶,就会触发等量扩容

双倍扩容:底层是hashmap,当桶的数量大于等于6.5个时,或者,溢出桶的数量过多时,就会触发双倍扩容

map无序性

由于扩容机制,取模运算时桶的数量变化,key的位置不确定

sync.map的底层原理

type Map struct {
    //互斥锁,用于锁定dirty map
    mu Mutex    
    
    //优先读map,支持原子操作,注释中有readOnly不是说read是只读,而是它的结构体。read实际上有写的操作
    read atomic.Value 
    
    // dirty是一个当前最新的map,允许读写
    dirty map[interface{}]*entry 
    
    // 主要记录read读取不到数据加锁读取read map以及dirty map的次数,当misses等于dirty的长度时,会将dirty复制到read
    misses int 
}

// readOnly 主要用于存储,通过原子操作存储在 Map.read 中元素。
type readOnly struct {
    // read的map, 用于存储所有read数据
    m       map[interface{}]*entry
    
    // 如果数据在dirty中但没有在read中,该值为true,作为修改标识
    amended bool 
}

// entry 为 Map.dirty 的具体map值
type entry struct {
    // nil: 表示为被删除,调用Delete()可以将read map中的元素置为nil
    // expunged: 也是表示被删除,但是该键只在read而没有在dirty中,这种情况出现在将read复制到dirty中,即复制的过程会先将nil标记为expunged,然后不将其复制到dirty
    //  其他: 表示存着真正的数据
    p unsafe.Pointer // *interface{}
}

sync.Map的原理很简单,使用了空间换时间策略,通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。

通过引入两个map将读写分离到不同的map,其中read map提供并发读和已存元素原子写,而dirty map则负责读写。

这样read map就可以在不加锁的情况下进行并发读取,当read map中没有读取到值时,再加锁进行后续读取,并累加未命中数,当未命中数大于等于dirty map长度,将dirty map上升为read map。

从之前的结构体的定义可以发现,虽然引入了两个map,但是底层数据存储的是指针,指向的是同一份值。

参考:https://segmentfault.com/a/1190000015242373

posted @ 2024-12-24 19:36  教诲you  阅读(93)  评论(0)    收藏  举报