Golang面试题库(sync.Map)

文章目录

  • sync.Map相关
  • [1. sync.Map的底层原理](#1. sync.Map的底层原理)
  • [2. read map 和 dirty map 之间的关联?](#2. read map 和 dirty map 之间的关联?)
  • [3. 为什么要设计 nil 和 expunged 状态?](#3. 为什么要设计 nil 和 expunged 状态?)
  • [4. sync.Map 适用的场景?](#4. sync.Map 适用的场景?)
  • [5. 你认为 sync.Map 有啥不足吗?](#5. 你认为 sync.Map 有啥不足吗?)
  • [6. 补充知识------分段锁 map 是什么](#6. 补充知识——分段锁 map 是什么)

sync.Map相关

1. sync.Map的底层原理

分析:

对于 sync.Map 的底层原理,我们回答的核心点围绕:sync.Map 如何保证并发安全,并减少锁操作的原理

回答:

空间换时间、数据的动态流转、entry 状态的设计

  • sync.Map 采用 空间换取时间的取舍策略 以及 实时动态的数据流转策略,期望使用 read map 来尽量将读、更新、删除操作的流量用 无锁化 的操作挡下来,避免去加锁去访问拥有全量数据的 dirty map

  • sync.Map 对于 k-v 对里面的 v,还设计了两种删除状态,一种是 nil 的软删除态,一种是 expunged 的硬删除态

    • nil 态可以拦截删除操作在 read map 这一层
    • expunged 态可以正确标识 dirty map 中有没有对应的逻辑删除的 key-entry

2. read map 和 dirty map 之间的关联?

分析:

  • read map 和 dirty map 作为 sync.Map 中的两个最重要的结构,它们互帮互助,read map 为 dirty map 尽量用轻便的原子操作挡住读、更新、删的流量,而 dirty map 也为 read map 提供最终的兜底手段

  • 同时 read map 和 dirty map 数据有互相流转的过程

回答:

  • read 可以当做 dirty 的保护层 map,尽量用轻便的原子操作将流量拦截在 read,防止加锁访问 dirty

  • dirty 当做 read 的兜底层 map,如果在 read 中没有完成的操作,最终需要加锁,然后尝试在 dirty 完成兜底

  • 当因为 miss read 而访问 dirty 的次数等于 dirty 的长度时,需要将 dirty map 提升到 read map,并置 dirty 为 nil

  • 当 dirty map 为 nil,会在 Store 里面触发 dirtyLocked 流程,这个流程会遍历 read map,将所有非删除状态的 k-entry 对写入到新 dirty 里面去

3. 为什么要设计 nil 和 expunged 状态?

分析:

  • dirty map 用于最终数据兜底,如果每次我们删除操作,直接删除 dirty 中对应 k-entey 对,但后面又对这个 k 进行写操作,那就导致多次加锁操作

  • 设计 nil 状态来标记 k-entry 对已经被逻辑删除了,但是 k-entry 还存在于 read map 和 dirty map 中,如果想对一个删除的 key,再进行写,那么可以通过在 read map 中解决

  • 而设计 expunged 状态是为了正确标识出 key-entry 对是否存在于 dirty map 中

  • nil 状态是软删除状态,代表逻辑上 k-v 被删除了,但是 k-entry 对还存在于 read map 和 dirty map 中

  • expunged 是硬删除态,也是逻辑上 k-v 删除了,但是 k-entry 对只存在 read map 中

回答:

  • nil 态是软删除态,可以让删除操作的流量在 read map 层挡住,防止加锁,去删除 dirty map 中的数据

  • expunged 态是硬删除态,也是逻辑上 k-v 删除了,但是 k-entry 对只存在 read map 中,能正确标识出 key-entry 对是否存在于 dirty map 中

4. sync.Map 适用的场景?

分析:

因为我们期望将更多的流量在 read map 这一层进行拦截,从而避免加锁访问 dirty map

对于更新、删除、读取,read map 可以尽量通过一些原子操作,让整个操作变得无锁化,这样就可以避免进一步加锁访问 dirty map

倘若写操作过多,sync.Map 基本等价于一把互斥锁 + map,所以我们要尽可能避免写多的场景,场景应用贴合读多、更新多、删多

回答:

sync.Map 是适用于读多、更新多、删多、写少的场景

5. 你认为 sync.Map 有啥不足吗?

分析:

对于 sync.map,在 dirtyLocked 流程中,需要遍历整个 read map,完成两步工作

  • 更新 read map 中的删除状态,将软删除态(nil)变成硬删除态(expunged)

  • 将 read map 中非删除态的 key-entry 对 写入到 dirty map 中

dirty map 和 read map 中的 key-entry 是相同的,但只有在 read map 中是有效(即没有被删除)时,才会被拷贝到 dirty map 中。删除状态(软删除和硬删除)会导致 entry 在 dirty map 中被过滤掉。

read map只会把非nil的逻辑上存在的共享给dirty map,并且把read map中为nil的entry改为expunged

dirtyLocked 这个流程是加锁的,如果在 sync.map 数据量比较大情况下,会引发性能抖动问题,因为这个时候其他 goroutine 想要访问 dirty map 拿锁就只能阻塞起来,存在很大的隐患

Godis 并发安全 map 实现------https://github.com/hdt3213/godis/blob/master/datastruct/dict/concurrent.go

回答:

sync.Map 不适用于写多的场景,因为写操作足够多的话,sync.Map 就相当于一把 sync.Mutex + Map

而且 sync.Map 中存在一个将 read map 数据流转到 dirty map 的过程,这个过程是线性时间复杂度,当 map 中 k-v 数量较多的时候,容易导致程序性能抖动,比如想要访问 sync.Map 拿锁操作的 goroutine 一直等待这个线性时间复杂度的过程完成

如何设计无锁map,面试应该怎么回答

无锁的话,只能用CAS来不断重试阻挡了 ​


6. 补充知识------分段锁 map 是什么

保证 map 的并发安全,最简单的做法就是直接用锁来进行保护,比如加读写锁保护,但是这样锁的粒度比较大,加锁直接锁住了整个 map,性能很差

分段锁的核心思想:

  • 数据分片:将整个 Map 划分为多个段,每个段包含独立的子 Map 和锁

  • 锁粒度细化:操作时仅锁定目标数据所在的段,其他段仍可并发访问,减少锁竞争

适用写多或 Key 分布均匀的场景在选择 sync.Map 和分段锁 map,优先考虑的就是应用场景下读写流量的比例,像 sync.Map 只适用了读多写少的场景,如果读写流量中写流量占比较大 或者 无法在使用之初确定读写流量比例,那就可以直接选择使用分段锁 map

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

相关推荐
3 小时前
java关于内部类
java·开发语言
好好沉淀3 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
To Be Clean Coder3 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
lsx2024063 小时前
FastAPI 交互式 API 文档
开发语言
你才是臭弟弟3 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端
VCR__3 小时前
python第三次作业
开发语言·python
码农水水3 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展
wkd_0073 小时前
【Qt | QTableWidget】QTableWidget 类的详细解析与代码实践
开发语言·qt·qtablewidget·qt5.12.12·qt表格
C澒4 小时前
面单打印服务的监控检查事项
前端·后端·安全·运维开发·交通物流
东东5164 小时前
高校智能排课系统 (ssm+vue)
java·开发语言