最近在使用 Golang 实现 HNSW 算法的多属性支持,在其中用到的 map 结构。然后 Map 并不是线程安全的,使用多线程的话在新版本的 Golang 中会直接出现 panic。

sync.Map 这个数据结构是线程安全的,它填补了 Map 线程不安全的缺陷,不过最好只在需要的情况下使用。它一般用于并发模型中对同一类 map 结构体的读写,或其他适用于 sync.Map 的情况。

关于 sync.Map 的源码解析文章:Go 1.9 sync.Map 揭秘

sync.Map 使用方法
StoreLoadOrStoreLoadRangeDelete

Store(key, value interface{})

说明: 存储一个设置的键值。

LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)

说明: 返回键的现有值 (如果存在),否则存储并返回给定的值,如果是读取则返回 true,如果是存储返回 false。

Load(key interface{}) (value interface{}, ok bool)

说明: 读取存储在 map 中的值,如果没有值,则返回 nil。OK 的结果表示是否在 map 中找到值。

Delete(key interface{})

说明: 删除键对应的值。

Range(f func(key, value interface{}) bool)

说明: 循环读取 map 中的值。

代码如下

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
44
45
46
47
48
49
50
51
52
53
54
package main

import (
"fmt"
"sync"
)

func () {
var m sync.Map


m.Store(1, "a")
m.Store(2, "b")

//LoadOrStore
//若key不存在,则存入key和value,返回false和输入的value
v, ok := m.LoadOrStore("1", "aaa")
fmt.Println(ok, v) //false aaa

//若key已存在,则返回true和key对应的value,不会修改原来的value
v, ok = m.LoadOrStore(1, "aaa")
fmt.Println(ok, v) //false aaa

//Load
v, ok = m.Load(1)
if ok {
fmt.Println("it's an existing key,value is ", v)
} else {
fmt.Println("it's an unknown key")
}

//Range
//遍历sync.Map, 要求输入一个func作为参数
f := func(k, v interface{}) bool {
//这个函数的入参、出参的类型都已经固定,不能修改
//可以在函数体内编写自己的代码,调用map中的k,v

fmt.Println(k, v)
return true
}
m.Range(f)

//Delete
m.Delete(1)
fmt.Println(m.Load(1))
}

// false aaa
// true a
// it's an existing key,value is a
// 1 a
// 2 b
// 1 aaa
// <nil> false

实际运用时遇到的问题

1
fatal error: sync: unlock of unlocked mutex
sync.Map

A Map must not be copied after first use.

Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.

Values containing the types defined in this package should not be copied.

创建完成的 sync.Map 是线程安全的,但是经过拷贝之后,两个 sync.Map 里面存储的是同一个 map(就是那个原生的,线程不安全的 map), mutex 无法起到保护作用,就线程不安全了。

参考:

sync.RWMutex+Map

利用传统的 sync.RWMutex+Map 实现并发安全的 Map:

1
2
3
4
var rwmap = struct{
sync.RWMutex
m map[string]string
}{m: make(map[string]sring)}

读数据时候,读锁锁定:

1
2
3
4
rwmap.RLock()
value:= rwmap.m["key"]
rwmap.RUnlock()
fmt.Println("key:", value)

写数据的时候,写锁锁定:

1
2
3
rwmap.Lock()
rwmap.m["key"]="value"
rwmap.Unlock()
两种 Map 性能对比