并发安全map
# 1、Map是否安全?
在 Golang 中,原生的 map 是非并发安全的,也就是说如果多个 goroutine 同时对 map 进行读写操作,会导致竞态条件和数据不一致的问题。为了确保 map 的并发安全,可以使用多种方法,下面介绍几种常见的解决方案。
# 2、解决方案
# 2.1、使用sync.Mutex
通过使用互斥锁 sync.Mutex 来保护 map 的读写操作,当多个goroutin
e同时访问一个map
时只有获得mutex
锁的能执行,其他goroutine
得阻塞直到锁释放才能继续,这样可以确保同一时间只有一个 goroutine
访问 map
。
package main
import (
"fmt"
"sync"
)
type SafeMap struct {
mu sync.Mutex
m map[string]int
}
func (sm *SafeMap) Set(key string, value int) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.m[key] = value
}
func (sm *SafeMap) Get(key string) (int, bool) {
sm.mu.Lock()
defer sm.mu.Unlock()
val, ok := sm.m[key]
return val, ok
}
func main() {
safeMap := SafeMap{
m: make(map[string]int),
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
safeMap.Set(fmt.Sprintf("key%d", i), i)
}(i)
}
wg.Wait()
for i := 0; i < 10; i++ {
key := fmt.Sprintf("key%d", i)
value, ok := safeMap.Get(key)
if ok {
fmt.Printf("key: %s, value: %d\n", key, value)
}
}
}
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
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
# 2.2、使用sync.RWMutex
sync.RWMutex 提供了读写锁功能,允许多个读操作同时进行,但写操作是互斥的。对于读多写少的场景,可以提高性能。
package main
import (
"fmt"
"sync"
)
type SafeMap struct {
rw sync.RWMutex
m map[string]int
}
func (sm *SafeMap) Set(key string, value int) {
sm.rw.Lock()
defer sm.rw.Unlock()
sm.m[key] = value
}
func (sm *SafeMap) Get(key string) (int, bool) {
sm.rw.RLock()
defer sm.rw.RUnlock()
val, ok := sm.m[key]
return val, ok
}
func main() {
safeMap := SafeMap{
m: make(map[string]int),
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
safeMap.Set(fmt.Sprintf("key%d", i), i)
}(i)
}
wg.Wait()
for i := 0; i < 10; i++ {
key := fmt.Sprintf("key%d", i)
value, ok := safeMap.Get(key)
if ok {
fmt.Printf("key: %s, value: %d\n", key, value)
}
}
}
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
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
# 2.3、使用sync.Map
(⭐)
# 2.3.1、声明
sync.Map 不像普通的 map 那样使用 make 创建,而是直接声明一个 sync.Map 变量即可。
var sm sync.Map
1
# 2.3.2、存储键值对
使用 Store 方法存储键值对。
sm.Store("key1", "value1")
1
# 2.3.3、加载键值对
使用 Load 方法加载键值对,如果键存在,返回对应的值和 true,否则返回 nil 和 false。
value, ok := sm.Load("key1")
if ok {
fmt.Println("key1:", value)
} else {
fmt.Println("key1 not found")
}
1
2
3
4
5
6
2
3
4
5
6
# 2.3.4、删除键值对
使用 Delete 方法删除指定键值对。
sm.Delete("key1")
1
# 2.3.5、加载或存储键值对
使用 LoadOrStore 方法,如果键存在则返回已存在的值和 true,否则存储新值并返回新值和 false。
value, loaded := sm.LoadOrStore("key2", "value2")
if loaded {
fmt.Println("key2 already exists with value:", value)
} else {
fmt.Println("key2 is set with value:", value)
}
1
2
3
4
5
6
2
3
4
5
6
# 2.3.6、迭代键值对
使用 Range 方法迭代所有键值对,参数是一个函数,函数返回 true 继续迭代,返回 false 停止迭代。
sm.Range(func(key, value interface{}) bool {
fmt.Println("key:", key, "value:", value)
return true
})
1
2
3
4
2
3
4
# 总结
以上三种方法都可以用来实现并发安全的 map,其中:
- 使用
sync.Mutex
和sync.RWMutex
需要手动管理锁的生命周期,适用于对锁的控制要求较高的场景。 - 使用
sync.Map
则更为简单易用,适合频繁读写的高并发场景。
特性 | sync.Mutex | sync.RWMutex | sync.Map |
---|---|---|---|
并发读写安全 | 是 | 是 | 是 |
实现复杂度 | 中 | 高 | 低 |
性能 | 中 | 高 | 高 |
适用场景 | 通用 | 读多写少 | 读写频繁 |
上次更新: 2024/07/08, 18:19:51