问题
- 数据竞争:Go语言的map不是线程安全的,多个goroutine同时读写map时会导致数据竞争,引发未定义行为,如程序崩溃或返回错误结果。例如多个goroutine同时向map中插入新键值对时,可能会破坏map的内部数据结构。
- 读取到脏数据:在并发写操作时,一个goroutine正在修改map,另一个goroutine读取该map,可能读取到部分修改后的数据,即脏数据,影响程序逻辑的正确性。
解决方案
- 使用
sync.Mutex
:
- 原理:通过互斥锁来保证同一时间只有一个goroutine能够访问map。在对map进行读写操作前,先获取锁,操作完成后释放锁。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var data = make(map[string]int)
func write(key string, value int, wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
data[key] = value
mu.Unlock()
}
func read(key string, wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
fmt.Println(data[key])
mu.Unlock()
}
- 使用
sync.RWMutex
:
- 原理:读写锁允许有多个读操作同时进行,但写操作时需要独占锁。读操作时获取读锁,写操作时获取写锁,适用于读多写少的场景。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var mu sync.RWMutex
var data = make(map[string]int)
func write(key string, value int, wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
data[key] = value
mu.Unlock()
}
func read(key string, wg *sync.WaitGroup) {
defer wg.Done()
mu.RLock()
fmt.Println(data[key])
mu.RUnlock()
}
- 使用
sync.Map
:
- 原理:
sync.Map
内部采用了分段锁的机制,通过两个map(read和dirty)来优化读写性能。read map存储了大部分被频繁访问的数据,并且可以无锁读取;dirty map则存储了read map中没有的数据,写操作先更新dirty map,当dirty map中的数据被读取一定次数后,会将dirty map提升为read map。
- 适用场景:适合高并发读写的场景,尤其是读操作频繁的情况。例如在分布式系统中存储一些缓存数据,多个节点并发读写这些缓存。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var data sync.Map
func write(key string, value int, wg *sync.WaitGroup) {
defer wg.Done()
data.Store(key, value)
}
func read(key string, wg *sync.WaitGroup) {
defer wg.Done()
if value, ok := data.Load(key); ok {
fmt.Println(value)
}
}