脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - 一文带你深入探究Go语言中的sync.Map

一文带你深入探究Go语言中的sync.Map

2023-04-28 16:59金刀大菜牙 Golang

在 Go 语言中,有一个非常实用的并发安全的 Map 实现:sync.Map,它是在 Go 1.9 版本中引入的。本文我们将深入探讨 sync.Map 的基本原理,帮助读者更好地理解并使用这个并发安全的 Map

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

1. Map 的基本实现原理

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

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

?
1
2
3
4
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 的结构体定义:

?
1
2
3
4
5
6
7
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 的定义:

?
1
2
3
4
type readOnly struct {
    m       map[interface{}]interface{}
    amended bool
}

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

?
1
2
3
4
5
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 中查找。读取操作的实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 中。写入操作的实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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。删除操作的实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 中的所有键值对进行合并,并返回所有未被删除的键值对。遍历操作的实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/7226604941727596581

延伸 · 阅读

精彩推荐
  • GolangGo中Writer和Reader接口的使用入门

    Go中Writer和Reader接口的使用入门

    本文主要介绍了Go中Writer和Reader接口的使用入门,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    宇宙之一粟11832022-09-25
  • Golang使用 Go 语言实现汉诺塔(Hanota)算法

    使用 Go 语言实现汉诺塔(Hanota)算法

    我最近重温了一下《猩球崛起》这部电影。在电影中,凯撒就玩了河内塔游戏。你还有印象吗?其实独自一人玩一些游戏是好难的(译者不知作者为何这么...

    马哥Linux运维9372022-04-18
  • Golanggolang架构设计开闭原则手写实现

    golang架构设计开闭原则手写实现

    这篇文章主要为大家介绍了golang架构设计开闭原则手写实例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    ioly7502022-10-28
  • Golanggolang对etcd存取和数值监测的实现

    golang对etcd存取和数值监测的实现

    这篇文章主要介绍了golang对etcd存取和数值监测的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们...

    麦穗儿5192020-05-28
  • GolangGO语言文件的创建与打开实例分析

    GO语言文件的创建与打开实例分析

    这篇文章主要介绍了GO语言文件的创建与打开的具体用法,实例分析了GO语言文件创建与打开操作中所涉及的函数具体用法,具有一定的参考借鉴价值,需要的朋...

    shichen20145962020-04-11
  • GolangGolang 删除文件并递归删除空目录的操作

    Golang 删除文件并递归删除空目录的操作

    这篇文章主要介绍了Golang 删除文件并递归删除空目录的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    wj1993957082021-06-10
  • GolangGo GORM 事务详细介绍

    Go GORM 事务详细介绍

    这篇文章主要介绍了Go GORM事务详细介绍,GORM 会在事务里执行写入操作创建、更新、删除,具体详细介绍需要的朋友可以参考下面文章的简单介绍...

    v2v17732022-07-31
  • Golang在golang xorm中使用postgresql的json,array类型的操作

    在golang xorm中使用postgresql的json,array类型的操作

    这篇文章主要介绍了在golang xorm中使用postgresql的json,array类型的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    WwJoyous11362021-05-28