首页 > 业界动态 > > 正文

一文带你深入探究Go语言中的sync.Map_全球快播

时间:2023-04-28 06:02:07 来源:脚本之家
目录
1. Map 的基本实现原理2. sync.Map 的实现原理2.1 sync.Map 的结构体定义2.2 sync.Map 的读取实现2.3 sync.Map 的写入实现2.4 sync.Map 的删除实现2.5 sync.Map 的遍历实现

在 Go 语言中,有一个非常实用的并发安全的 Map 实现:sync.Map,它是在 Go 1.9 版本中引入的。相比于标准库中的 map,它的最大特点就是可以在并发情况下安全地读写,而不需要加锁。在这篇博客中,我们将深入探讨 sync.Map 的基本原理,帮助读者更好地理解并使用这个并发安全的 Map。

1. Map 的基本实现原理

在介绍 sync.Map 的基本实现原理之前,我们需要先了解一下 Go 语言标准库中的 map 实现原理。在 Go 中,map 是基于哈希表实现的。当我们向 map 中添加元素时,它会根据 key 计算出一个哈希值,然后将这个值映射到一个桶中。如果该桶中已经有了元素,它会遍历桶中的元素,查找是否已经存在相同的 key,如果存在就更新对应的值,否则就添加一个新的键值对。


(资料图片)

下面是一个简单的 map 示例:

m := make(map[string]int)
m["a"] = 1
m["b"] = 2
fmt.Println(m["a"]) // Output: 1

当我们运行这段代码时,Go 语言会自动帮我们分配一个哈希表和若干个桶,然后将键值对添加到对应的桶中。这样,当我们需要访问某个 key 对应的值时,Go 语言会根据哈希值快速定位到对应的桶,然后遍历桶中的元素,查找是否有相同的 key,如果找到了就返回对应的值。

2. sync.Map 的实现原理

sync.Map 是 Go 语言标准库中的一个并发安全的 Map 实现,它可以在并发情况下安全地读写,而不需要加锁。那么,它是如何实现这种并发安全性的呢?下面我们就来一步步地解析 sync.Map 的实现原理。

2.1 sync.Map 的结构体定义

首先,让我们来看一下 sync.Map 的结构体定义:

type Map struct {
    mu      sync.Mutex
    read    atomic.Value // readOnly
    dirty   map[interface{}]interface{}
    misses  int
    dirtyLocked uintptr
}

从上面的代码中可以看出,sync.Map 的实现主要是依赖于一个互斥锁(sync.Mutex)和两个 map(read 和 dirty)。其中,read 和 dirty 的作用分别是什么呢?我们先来看一下 read 的定义:

type readOnly struct {
    m       map[interface{}]interface{}
    amended bool
}

可以看到,read 只有一个成员 m,它是一个 map 类型。而 amended 则表示 read 中的键值对是否被修改过。接下来,我们来看一下 dirty 的定义:

type dirty struct {
    m       map[interface{}]interface{}
    dirty   map[interface{}]bool
    misses  int
}

和 read 不同的是,dirty 中包含了两个 map:m 和 dirty。其中,m 存储了被修改过的键值对,而 dirty 则存储了哪些键值对被修改过。

2.2 sync.Map 的读取实现

在 sync.Map 中,读取操作非常简单,直接从 readOnly 中的 m 中查找即可。如果 readOnly 中的键值对被修改过,则需要从 dirty 中查找。读取操作的实现代码如下:

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    read, _ := m.read.Load().(readOnly)
    value, ok = read.m[key]
    if !ok && read.amended {
        m.mu.Lock()
        read, _ = m.read.Load().(readOnly)
        value, ok = read.m[key]
        if !ok && read.amended {
            value, ok = read.m[key]
        }
        m.mu.Unlock()
    }
    return
}

在这段代码中,我们首先从 readOnly 中的 m 中查找键值对。如果键值对不存在且 readOnly 中的键值对被修改过,则需要获取互斥锁,并重新从 readOnly 中查找。如果还是没有找到,那么就从 dirty 中查找。

2.3 sync.Map 的写入实现

在 sync.Map 中,写入操作需要分两步完成。首先,我们需要判断 readOnly 中的键值对是否被修改过,如果没有被修改过,则直接将键值对添加到 readOnly 中的 m 中即可。否则,我们需要获取互斥锁,然后将键值对添加到 dirty 中的 m 中,并将对应的键添加到 dirty 中的 dirty 中。写入操作的实现代码如下:

func (m *Map) Store(key, value interface{}) {
    read, _ := m.read.Load().(readOnly)
    if v, ok := read.m[key]; !ok && !read.amended {
        m.mu.Lock()
        read, _ = m.read.Load().(readOnly)
        if v, ok := read.m[key]; !ok {
            read = readOnly{m: read.m, amended: true}
        }
        read.m[key] = value
        m.read.Store(read)
        m.mu.Unlock()
    } else {
        m.mu.Lock()
        dirty := m.dirtyLocked != 0
        if !dirty {
            m.dirtyLocked = 1
            m.dirty = make(map[interface{}]interface{})
        }
        m.dirty[key] = value
        if !ok {
            m.dirty[key] = value
            m.dirty[key] = true
        }
        if dirty {
            m.mu.Unlock()
            return
        }
        m.read.Store(readOnly{m: read.m, amended: true})
        m.mu.Unlock()
    }
}

在这段代码中,我们首先从 readOnly 中的 m 中查找键值对。如果键值对不存在且 readOnly 中的键值对没有被修改过,则需要获取互斥锁,并重新从 readOnly 中查找。如果还是没有找到,则将键值对添加到 readOnly 中的 m 中,并将 amended 设置为 true。否则,我们需要获取互斥锁,并将键值对添加到 dirty 中的 m 中,并将对应的键添加到 dirty 中的 dirty 中。如果 dirty 中已经存在该键,则只需要更新 dirty 中的键值即可。如果 dirty 中没有该键,则需要在 dirty 中添加该键,并将该键的 dirty 置为 true。

接下来,我们需要判断 dirty 是否被锁定。如果 dirty 被锁定,则直接退出函数。否则,我们需要将 readOnly 中的 amended 设置为 true,并将 readOnly 存储回 read 中。

2.4 sync.Map 的删除实现

在 sync.Map 中,删除操作也需要分两步完成。首先,我们需要判断 readOnly 中的键值对是否被修改过,如果没有被修改过,则直接从 readOnly 中的 m 中删除键值对即可。否则,我们需要获取互斥锁,然后将键添加到 dirty 中的 dirty 中,并将 dirty 中的对应键的值设置为 false。删除操作的实现代码如下:

func (m *Map) Delete(key interface{}) {
    read, _ := m.read.Load().(readOnly)
    if _, ok := read.m[key]; ok || read.amended {
        m.mu.Lock()
        read, _ = m.read.Load().(readOnly)
        if _, ok := read.m[key]; ok || read.amended {
            if m.dirty == nil {
                m.dirty = make(map[interface{}]interface{})
            }
            m.dirty[key] = false
            m.dirty[key] = true
            m.read.Store(readOnly{m: read.m, amended: true})
        }
        m.mu.Unlock()
    }
}

在这段代码中,我们首先从 readOnly 中的 m 中查找键值对。如果键值对存在或者 readOnly 中的键值对被修改过,则需要获取互斥锁,并重新从 readOnly 中查找。如果还是没有找到,则将键添加到 dirty 中的 dirty 中,并将 dirty 中的对应键的值设置为 false。接下来,我们需要判断 dirty 是否为 nil,如果为 nil,则需要将 dirty 初始化为一个空 map。然后,我们将键添加到 dirty 中,并将 dirty 中的对应键的值设置为 true。最后,我们将 readOnly 中的 amended 设置为 true,并将 readOnly 存储回 read 中。

2.5 sync.Map 的遍历实现

在 sync.Map 中,遍历操作需要将 readOnly 和 dirty 中的所有键值对进行合并,并返回所有未被删除的键值对。遍历操作的实现代码如下:

func (m *Map) Range(f func(key, value interface{}) bool) {
    read, _ := m.read.Load().(readOnly)
    if read.amended {
        m.mu.Lock()
        read, _ = m.read.Load().(readOnly)
        if read.amended {
            read = readOnly{
                m: merge(read.m, m.dirty),
            }
            read.amended = false
            m.read.Store(read)
            m.dirty = nil
        }
        m.mu.Unlock()
    }
    for k, v := range read.m {
        if !f(k, v) {
            break
        }
    }
}
​
func merge(m1, m2 map[interface{}]interface{}) map[interface{}]interface{} {
    if len(m1) == 0 && len(m2) == 0 {
        return nil
    }
    if len(m1) == 0 {
        return m2
    }
    if len(m2) == 0 {
        return m1
    }
    m := make(map[interface{}]interface{})
    for k, v := range m1 {
        m[k] = v
    }
    for k, v := range m2 {
        if _, ok := m[k]; !ok || !v.(bool) {
            m[k] = v
        }
    }
    return m
}

在这段代码中,我们首先从 readOnly 中获取所有的键值对,并检查是否有键值对被修改过。如果键值对被修改过,则需要获取互斥锁,并将 readOnly 和 dirty 中的键值对合并,然后将合并后的键值对存储回 readOnly 中,并将 dirty 设置为 nil。接下来,我们遍历 readOnly 中的所有键值对,并调用 f 函数来处理键值对。如果 f 函数返回 false,则遍历过程结束。

在这个 Range 函数中,我们还实现了一个名为 merge 的辅助函数,用于合并两个 map。在合并过程中,我们首先判断两个 map 是否为空,如果为空,则直接返回 nil。如果其中一个 map 为空,则返回另一个 map。否则,我们需要将 m1 中的键值对全部添加到新的 map 中,并逐个遍历 m2 中的键值对。如果 m2 中的键不存在于新的 map 中,或者 m2 中的键被删除,则将其添加到新的 map 中。

以上就是一文带你深入探究Go语言中的sync.Map的详细内容,更多关于Go语言sync.Map的资料请关注脚本之家其它相关文章!

关键词:
x 广告
知识领域 诺基亚强制开机怎么按

1、首先将手机正常关机;2、同时按住拨号键、星号键、数字三号键;3、再按住挂机键打开电源直到英文诺基亚字样出现,此过程不能松开任何一个

共同抗疫!郑州71岁党员志愿者每天爬6栋楼,提醒800多户居民做核酸

1月4日,郑州市新冠肺炎疫情防控指挥部办公室发布通告对部分区域实施管控措施。这是近半年来,新冠疫情第3次造访郑州。截止至1月10日24时,

安阳疫情最新消息|1月11日8时至12日8时安阳市新增本土确诊病例39例,累计123例

安阳疫情最新消息2022年1月11日8时至12日8时安阳市新型冠状病毒肺炎疫情最新情况1月11日8时至12日8时,全市新增本土确诊病例39例(汤阴县38

迅速高效!“河南抗疫互助通道”网上网下联动 获群众点赞

核酸检测24小时了还没查到结果,怎么办?现在高速下站口的管控政策到底是什么样子的?从洛阳老城区自驾走高速回郑州,收费站还劝返吗?苏八

信阳市疫情防控工作视频调度会召开 陈志伟出席会议

1月11日,市疫情防控工作视频调度会召开。代市长陈志伟出席会议并讲话。听取有关疫情防控重点工作和各县区(管理区、开发区)疫情防控工作

全国最新疫情消息:11日新增本土确诊病例166例 天津市今日启动第二轮全员核酸检测

11日0—24时,31个省(自治区、直辖市)和新疆生产建设兵团报告新增新冠肺炎本土确诊病例166例(河南118例,其中安阳市65例、许昌市41例、

x 广告

Copyright ©  2015-2022 亚洲评测网版权所有  备案号:豫ICP备20022870号-9   联系邮箱:553 138 779@qq.com