sync.Map 源码学习

    golang 线程安全的 Map

作者水平有限,而并发博大精深. 如文章中有任何错误, 希望读者不吝指出.谢谢!

章节目录

  1. Map 基本类型定义
  2. Store
  3. Load
  4. Delete
  5. Range

Map 基本类型定义##

Map

这里我们先看一下 Map 都涉及到那些类型.

type Map struct {// 互斥锁. 用于互斥的读写 dirty.mu Mutex// 值得声明的一点, read 和 dirty 中的 entry 是同一个.read atomic.Value   // 该属性的实际类型为 readOnly 类型.dirty map[interface{}]*entrymisses int
}

readOnly

type readOnly struct {m       map[interface{}]*entryamended bool // 表示 dirty 中是否包含一些不存在于 read 中的 key.
}

entry

// 用来表示 Map 中的一个 Value 值.
type entry struct {p unsafe.Pointer // *interface{}
}

vars

// 当某个 entry 的值为 expunged, 表示该 entry 已经被标记为删除状态, 等待被删除.
var expunged = unsafe.Pointer(new(interface{}))

Map : Store()##

Store 流程:

  1. 先在 read 中查找相应的 key, 如果该 key 已存在且该 entry 没有被标记为已删除, 则更新该 entry 的值并返回.
  2. 然后在 dirty 中寻找该 key, 如果该 key 存在于 dirty 中, 则更新该 entry 并返回.
  3. 如果 read 和 dirty 中都不包含该 key:
    a) 如果当前 read 中包含的 key 和 dirty 中包含的 key 相同且 dirty 不为空, 则将 read 中不为空的 entry 复制到 dirty 中. 并将
    b) 将新的 key-value 存在 dirty 中.
    这里对于 read 的操作是不需要加锁的.而对 dirty 的操作是需要加锁的.
    对于 read 的操作不会涉及到添加新的 key, 只是读取或者更新 entry 的值. 而 entry 是 atomic.Value 类型的, 通过相应的原子操作方法进行,因此不需要加锁. 而对 dirty 的操作会涉及到往一个普通 map 中添加新的 key-value, 因此需要加锁.
// 创建一个 entry
func newEntry(i interface{}) *entry {return &entry{p: unsafe.Pointer(&i)}
}// Map 中的 entry 有两种合法的状态 : expunged(被标记为删除状态), 正常状态.
// 如果 entry 被标记为删除状态, tryStore 返回 false
// 否则, 会将 i 保存到 map 中, tryStore 返回 truefunc (e *entry) tryStore(i *interface{}) bool {p := atomic.LoadPointer(&e.p)if p == expunged {return false}for {// 使用 CAS 设置 entry 的值. 如果设置成功, 返回 trueif atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {return true}// 这里的理解有待进一步验证.// 在刚刚进入方法 tryStore 时我们取出 entry 的值, 存储于 p.// 但是有可能在从第一句代码到使用 CAS 设置 entry 的值期间, // entry.p 被其他线程修改(删除), 导致 CAS 操作失败.p = atomic.LoadPointer(&e.p)if p == expunged {return false}}
}// 判断该 entry 的值是否已经被标记为删除状态.
// 如果该 entry 的值为 expunged, 说明该 entry 的值已经被标记为删除, 此时将该 entry 置为 nil.
func (e *entry) unexpungeLocked() (wasExpunged bool) {return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}func (e *entry) storeLocked(i *interface{}) {atomic.StorePointer(&e.p, unsafe.Pointer(i))
}// 判断该 entry 的值是否已经被标记为删除状态
// 如果该 entry 的值为 nil, 则将该 entry 修改为 expunged.
func (e *entry) tryExpungeLocked() (isExpunged bool) {p := atomic.LoadPointer(&e.p)for p == nil {if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {return true}p = atomic.LoadPointer(&e.p)}return p == expunged
}// 如果 dirty map为空,则把 read 中数据拷贝到 dirty 中.
func (m *Map) dirtyLocked() {if m.dirty != nil {return}read, _ := m.read.Load().(readOnly)m.dirty = make(map[interface{}]*entry, len(read.m))for k, e := range read.m {// 将所有的 nil 的 entry 设置为 expunged// 拷贝不为 expunged 的 entryif !e.tryExpungeLocked() {m.dirty[k] = e}}
}func (m *Map) Store(key, value interface{}) {read, _ := m.read.Load().(readOnly)// 如果 Map 中相应的 key 已经存在, 且没有被标记为删除状态, // 将 value 写入, 并返回.if e, ok := read.m[key]; ok && e.tryStore(&value) {return}// 如果 read 中没有对应的 key, 此时则需要往 dirty 中写入 key-value.// 注意, 对于 dirty 的操作需要加锁.m.mu.Lock()// 在获取锁期间, 有可能 read 被其他线程修改, // 因此在此检查 key 是否已经存在于 read 中.read, _ = m.read.Load().(readOnly)//在获取锁期间, read 中被存入 key 相关的 entry.if e, ok := read.m[key]; ok {// 如果 entry 被标记为删除状态, 删除原对象, 并写入新对象 e.if e.unexpungeLocked() { m.dirty[key] = e}// 如果 entry 未被标记为删除状态, 说明原先 entry 也是一个合法数据, // 则直接更新该 entry 的值.e.storeLocked(&value)} else if e, ok := m.dirty[key]; ok { // read 中不存在 key, 而 dirty 中存在该 key. // 直接更新该 key 所对应的 entrye.storeLocked(&value)} else { // read 和 dirty 中都不存在该 key.if !read.amended {m.dirtyLocked()m.read.Store(readOnly{m: read.m, amended: true}) // 将 amended 值为true, 标记 dirty 中存在 read 没有的 key.}m.dirty[key] = newEntry(value)}m.mu.Unlock()
}

Map 的方法: Load()##

Load 流程:

  1. 先在 read 中查找该 key, 如果找到直接返回.
  2. 否则返回在 dirty 中查找该 key 的结果(可能为空), 并增加 misses 的值. 当misses 的值大于 dirty 的长度时, 将 dirty 中数据转储于 read 中.(之所以转存, 是因为访问 read 不需要锁).
// 增加 misses 计数. 
// 在 misses 大于 dirty 长度时, 将 dirty 复制到 read 中. 并清空 dirty, 重置 misses
func (m *Map) missLocked() {m.misses++if m.misses < len(m.dirty) {return}m.read.Store(readOnly{m: m.dirty}) // 注意, 这里隐式的将 amended 置为 false.m.dirty = nilm.misses = 0
}// 该方法用来在 Map 中查找与 key 关联的 value. 未找到, 返回 nil.
// ok 返回值表示是否找到相关的 key.
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {// 查找 key 时, 总是先在 Map.read 字段中查找. 找到则返回该 value.read, _ := m.read.Load().(readOnly)e, ok := read.m[key]// 如果 read 中不存在该 key, 而 dirty 中存在该 keyif !ok && read.amended {m.mu.Lock()read, _ = m.read.Load().(readOnly)e, ok = read.m[key]if !ok && read.amended {e, ok = m.dirty[key]m.missLocked()}m.mu.Unlock()}if !ok {return nil, false}return e.load()
}

Map 的方法: Delete()##

func (e *entry) delete() (hadValue bool) {for {p := atomic.LoadPointer(&e.p)// 如果该元素已经被标记为删除状态, 直接返回.if p == nil || p == expunged {return false}// 如果该元素为正常状态, 则将该 entry 置为 nilif atomic.CompareAndSwapPointer(&e.p, p, nil) {return true}}
}func (m *Map) Delete(key interface{}) {read, _ := m.read.Load().(readOnly)e, ok := read.m[key]// read 中不存在该 keyif !ok && read.amended {m.mu.Lock()read, _ = m.read.Load().(readOnly)e, ok = read.m[key]if !ok && read.amended {delete(m.dirty, key)}m.mu.Unlock()}// read 中存在该 keyif ok {e.delete()}
}

Map 的方法: Range()##


func (m *Map) Range(f func(key, value interface{}) bool) {read, _ := m.read.Load().(readOnly)// 如果 Map 元素存储于 dirty 中, 先将数据转存到 read 中.if read.amended {m.mu.Lock()read, _ = m.read.Load().(readOnly)if read.amended {read = readOnly{m: m.dirty}m.read.Store(read)m.dirty = nilm.misses = 0}m.mu.Unlock()}for k, e := range read.m {v, ok := e.load()if !ok {continue}if !f(k, v) {break}}
}

感谢 https://juejin.im/post/5b1b3d785188257d45297d0a 文章作者的分享.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499696.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASN.1 学习

ASN.1 章节目录 简介常用数据类型 2.1 常见的简单类型 2.2 结构类型Basic Encoding RulesDistinguished Encoding Rules编码示例 5.1 BIT STRING 5.2 IA5String 5.3 INTEGER 5.4 NULL 5.5 OCTET STRING 5.6 UTCTime 5.6 OBJECT IDENTIFIER编码 Name (X.501 type) 参考 http://…

证书体系: PFX 文件格式解析

原文同时发布于本人个人博客&#xff1a; https://kutank.com/blog/cert-pfx/ 章节目录 PFX 简介PFX 格式解析 2.1 最外层结构 2.2 AuthenticatedSafe 结构 参考 https://tools.ietf.org/html/rfc7292. PFX 简介## 以下引用自维基百科 在密码学中&#xff0c;PKCS #12 定义了…

C10K 非阻塞 Web 服务器

本文由作为 Going Concurrency in Go 的作者 Nathan Kozyra 撰写, 解决了互联网上最著名,最受尊敬的挑战之一, 并试图通过核心 Go 包来解决它. 原文地址: https://hub.packtpub.com/c10k-non-blocking-web-server-go/ 我们已经构建了一些可用的应用程序,并且可以在日常使用的真…

MD5 算法描述及实现

MD5 算法的原理及实现 章节目录 简介算法描述 实现 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 简介## Wiki对其的描述: MD5消息摘要算法&#xff08;英语&#xff1a;MD5 Message-Digest Algorithm&…

SHA 算法描述与实现

SHA 算法的原理及实现 章节目录 简介算法描述 2.1 数据准备 2.1.1 数据填充 2.1.2 数据分块 2.1.3 设置初始 Hash 值 2.2 Hash 计算 2.2.1 SHA-1 2.2.2 SHA-256 2.2.3 SHA-512实现 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者…

SHA算法描述及实现

SHA 算法的原理及实现 章节目录 简介算法描述 2.1 数据准备 2.1.1 <数据填充 2.1.2 数据分块 2.1.3 设置初始 Hash 值 2.2 Hash 计算 2.2.1 SHA-1 2.2.2 SHA-256 2.2.3 SHA-512实现<b>作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, …

CNG 关于 Key 相关的操作

章节目录 简介创建 Key查看系统中的 Key从 Windows Store 导出 key导入 Key 到 Windows Store<b>作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢!</b> 简介 CNG 全称 Cryptography API: Next G…

Golang 词法分析器浅析

浅析 Go 语言的词法分析器 章节目录 简介TokenScanner例子 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 简介## 在本文我们将简单的走读 Go 语言的词法分析器实现(go/scanner/scanner.go). 本文基于 G…

如何读懂 C 语言复杂的声明

如何读懂 C 语言复杂的声明 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 参考<<C专家编程>> 废话 虽说 C 语言相比于其他更高级的语言来讲&#xff0c;有着非常精简的语法结构&#xff0c;对…

C 语言笔记: 链表节点实现技巧--struct的妙用

链表节点实现技巧–struct的妙用 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 废话 C 语言虽然只提供了非常简单的语法&#xff0c;但是丝毫不影响 C 语言程序员使用 C 来实现很多让人叹为观止的高级功能…

协议簇: Media Access Control(MAC) Frame 解析

Media Access Control(MAC) Frame 解析 前言 千里之行&#xff0c;始于足下。 因为个人从事网络协议开发&#xff0c;一直想深入的学习一下协议族&#xff0c;从这篇开始&#xff0c;我将开始记录分享我学习到的网络协议相关的知识 简介 引用百度百科的描述&#xff1a; 数…

协议簇:Ethernet Address Resolution Protocol (ARP) 解析

简介 前面的文章中&#xff0c;我们介绍了 MAC Frame 的帧格式。我们知道&#xff0c;在每个 Ethernet Frame 中都分别包含一个 48 bit 的源物理地址和目的物理地址. 对于源地址很容易理解&#xff0c;该地址可以直接从硬件上读取. 但是对于一个网络节点&#xff0c;他怎么知道…

协议簇:IPv4 解析

简介 IP 是一种无连接的协议. 操作在使用分组交换的链路层&#xff08;如以太网&#xff09;上。此协议会尽最大努力交付数据包。 尽最大努力意味着&#xff1a; IP 协议不保证数据的可靠传输, 没有流量控制机制, 不保证传输序列(意味着 IP 数据包会在传输过程中乱序), 没有…

协议簇:ICMP 解析

简介 ICMP 是 Internet Control Message Protocol 的简写. 它主要用来调试网络通信环境中存在的问题. 比如&#xff0c;当 IP 数据包总是无法正常的发送到目的地址, 当网关没有足够的 buffer 来转发对应的数据包 等问题. 值得一提的是&#xff0c;它属于网络层&#xff0c;不属…

协议簇:TCP 解析: 基础

简介 本文我们将从 RFC 学习一下 RFC793 中描述的 TCP 协议. 这将区别于通常讲解计算机网络书籍中所描述的 TCP. 但他们必然是相统一的&#xff0c;不会互相冲突. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&#xff1a;建立连接 协议…

协议簇:TCP 解析: 建立连接

简介 接前文 协议簇&#xff1a;TCP 解析: 基础&#xff0c; 我们这篇文章来看看 TCP 连接建立的过程&#xff0c;也就是众所周知的”三次握手“的具体流程. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&#xff1a;建立连接 协议簇&a…

协议簇:TCP 解析: 连接断开

简介 接前文 协议簇&#xff1a;TCP 解析: 建立连接&#xff0c; 我们这篇文章来看看 TCP 连接断开的过程&#xff0c;也就是众所周知的”四次挥手“的具体流程. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&#xff1a;建立连接 协议…

协议簇:TCP 解析: Sequence Number

简介 序列号&#xff08;Sequence Number&#xff09; 是 TCP 协议中非常重要的一个概念&#xff0c;以至于不得不专门来学习一下。这篇文章我们就来解开他的面纱. 在 TCP 的设计中&#xff0c;通过TCP协议发送的每个字节都对应于一个序列号. 由于每个字节都有自己的序列号&a…

协议簇:TCP 解析:TCP 数据传输

简介 前面&#xff0c;我们分别介绍了 TCP 基础知识以及连接的建立和关闭&#xff0c;以及最重要的 Sequence Number 的概念. 本篇文章&#xff0c;我们来介绍一下 TCP 如何传输数据. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&…

CodeTank iOS App Technical Support

CodeTank iOS App Technical Support For All Email: z253951598outlook.com TEL: 86-17782749061 App Screen Shoots