首页 > 其他分享 >golang sync.Map之如何设计一个并发安全的读写分离结构?

golang sync.Map之如何设计一个并发安全的读写分离结构?

时间:2024-03-19 16:25:28浏览次数:13  
标签:Map map read sync golang dirty 数据

在 golang中,想要并发安全的操作map,可以使用sync.Map结构,sync.Map 是一个适合读多写少的数据结构,今天我们来看看它的设计思想,来看看为什么说它适合读多写少的场景。

如下,是golang 中sync.Map的数据结构,其中 属性read 是 只读的 map,dirty 是负责写入的map,sync.Map中的键值对value值本质上都是entry指针类型,entry中的p才指向了实际存储的value值

// sync.Map的核心数据结构
type Map struct {
    mu Mutex                        // 对 dirty 加锁保护,线程安全
    read atomic.Value                 // read 只读的 map,充当缓存层
    dirty map[interface{}]*entry     // 负责写操作的 map,当misses = len(dirty)时,将其赋值给read
    misses int                        // 未命中 read 时的累加计数,每次+1
}
// 上面read字段的数据结构
type readOnly struct {
    m  map[interface{}]*entry // 
    amended bool // Map.dirty的数据和这里read中 m 的数据不一样时,为true
}

// 上面m字段中的entry类型
type entry struct {
    // value是个指针类型
    p unsafe.Pointer // *interface{}
}

我们从一个sync.Map的数据写入和数据查询 两个过程来分析这两个map中数据的变化。

我将不展示具体的代码,仅仅讲述数据的流动,相信懂了这个以后再去看代码应该不难。

步骤一: 首先是一个初始的sync.Map 结构,我们往其中写入数据,数据会写到dirty中,同时,由于sync.Map 刚刚创建,所以read map还不存在,所以这里会先初始化一个read map 。amended 是read map中的一个属性,为true代表 dirty 和read中数据不一致。

image.png

步骤二: 接着,如果后续再继续写入新数据,
在read map没有从dirty 同步数据之前,即amended 变为false之前,再写入新键值对都只会往dirty里写。

image.png

步骤三: 如果有读操作,sync.Map 都会尽可能的让其先读read map,read map读取不到并且amended 为true,即read 和dirty 数据不一致时,会去读dirty,读dirty的过程是上锁的。

image.png

步骤四: 当读取read map中miss次数大于等于dirty数组的长度时,会触发dirty map整体更新为readOnly map,并且这个过程是阻塞的。更新完成后,原先dirty会被置为空,amended 为false,代表read map同步了之前所有的数据。如下图所示,

image.png

整体更新的逻辑是直接替换变量的值,并非挨个复制,

func (m *Map) missLocked() {
    m.misses++
    if m.misses < len(m.dirty) {
        return
    }
    
    // 将dirty置给read,因为穿透概率太大了(原子操作,耗时很小)
    m.read.Store(readOnly{m: m.dirty})
    m.dirty = nil
    m.misses = 0
}

步骤五: 如果后续sync.Map 不再插入新数据,那么读取时就可以一直读取read map中的数据了,直接读取read map 中的key是十分高效的,只需要用atomic.Load 操作 取到readOnly map结构体,然后从中取出特定的key就行。

如果读miss了,因为没有插入新数据,read.amended=false 代表read 是保存了所有的k,v键值对,读miss后,也不会再去读取dirty了,也就不会有读dirty加锁的过程。

// 上面read字段的数据结构
type readOnly struct {
    m  map[interface{}]*entry // 
    amended bool // Map.dirty的数据和这里read中 m 的数据不一样时,为true
}

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    // 因read只读,线程安全,优先读取
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    
    // 如果read没有,并且dirty有新数据,那么去dirty中查找(read.amended=true:dirty和read数据不一致)
    // 暂时省略 后续代码
    .......
	
    }

上面的获取key对应的value过程甚至比RWMutex 读锁下获取map中的value还要高效,毕竟RWmutex 读取时还需要加上读锁,其底层是用atomic.AddInt32 操作,而sync.Map 则是用 atomic.load 获取map,atomic.AddInt32 的开销比atomic.load 的开销要大。

标签:Map,map,read,sync,golang,dirty,数据
From: https://www.cnblogs.com/hobbybear/p/18083224

相关文章

  • Golang案例开发之gopacket监听网卡抓包(2)
    文章目录前言二、实践监听网卡抓包1.代码2.知识点OpenLive方法SetBPFFilter断言总结前言本节实战,监听指定网卡,进行网络抓包,根据分层,解析不同分层包的内容。二、实践监听网卡抓包1.代码代码如下(示例):packagemainimport( "fmt" "log" "......
  • QSignalMapper的使用和使用场景
    QSignalMapper的使用和使用场景 目录QSignalMapper的使用和使用场景常见场景下面是参考。可看可不看这篇写的不错,搬运为Markdown了可以看一下参考 QSignalMapper的使用和使用场景QSignalMapper类收集了一系列的无参信号,然后使用相对于信号发送者来说的整数......
  • Semaphore源码解析
    Semaphorehttps://www.bilibili.com/video/BV1Ae411C7xr/publicclassSemaphoreimplementsjava.io.Serializable同Reetrantlock在Sync继承AQSabstractstaticclassSyncextendsAbstractQueuedSynchronizer可以指定Sync是否是公平锁,默认非公平permits为设置AQS内stat......
  • promise与async/await连用全部请求结束时获取请求结果
    async/await获取请求结束时机,拿到结果(非promise类型的结果)constgetModalData=useCallback(async()=>{constresult=awaitsendRequest(currentCabinet)setData(result)},[currentCabinet])定义promise:因为此处的请求方法结果返回的时promise......
  • Golang多线程打印ABC
    packagemainimport("fmt""sync")funcThreeG(){varch1,ch2,ch3=make(chanstruct{}),make(chanstruct{}),make(chanstruct{})varwgsync.WaitGroupwg.Add(3)gofunc(sstring){deferwg.Done......
  • Go04-数组+切片+map
    Go4-数组+切片+map1.数组的定义、赋值、访问和遍历//1数组用来存放多个同一类型的数据,在Go中数组是值类型。//2数组的定义、赋值和遍历。//定义数字。vararrs[2]int//给数组元素赋值。arrs[0]=0arrs[1]=2//02fori:=0;i<len(arrs);i++{fmt.P......
  • 【STL】 C++常用容器介绍系列(一)----(map、set、stack)
    目录一、map系列1、map介绍2、unordered_map介绍3、map和unordered_map的选择二、set系列1、set介绍2、unordered_set介绍3、set和unordered_set的选择三、如何遍历和查询map和set1、map的遍历2、map的查询3、set的遍历4、set的查询四、stack介绍和操作stack的方......
  • golang fasthttp服务端的简单实现
    使用示例:packagemainimport("github.com/buaazp/fasthttprouter""github.com/valyala/fasthttp""log")funcmain(){//创建路由r:=fasthttprouter.New()r.GET("/",Index)iferr:=fasthttp.Listen......
  • 【golang语言】
    目录数据类型基础数据类型boolstringintint8int16int32int64uintuint8uint16uint32uint64uintptrbyterunefloat32float64complex64complex128类型转换go语言不允许隐式转换别名类型和原有类型也不能进行隐式转换类型的预定义值......
  • 问题分析 | 为什么主库Waiting for semi-sync ACK from slave会阻塞set global super_
    作者:卢文双资深数据库内核研发本文首发于2023-12-0321:33:21https://dbkernel.com问题描述为什么主库上有Waitingforsemi-syncACKfromslave的时候,执行setglobalsuper_read_only=ON会导致等待全局读锁?问题复现MySQL主从高可用集群,semi-sync超时无限大:setglob......