方法一:使用sync.Mutex
- 原理:通过
sync.Mutex
来保护对map
的读写操作,在进行读写前加锁,操作完成后解锁,从而避免并发冲突。
- 优点:
- 实现简单,易于理解和使用。
- 可以灵活控制锁的粒度,比如只对关键的读写操作加锁。
- 缺点:
- 性能问题,在高并发场景下,频繁的加锁和解锁会带来额外的开销,降低程序的并发性能。
- 可能会导致死锁,如果锁的使用不当。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
mu sync.Mutex
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()
value := data[key]
fmt.Printf("Read key %s, value %d\n", key, value)
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go write("key1", 1, &wg)
go read("key1", &wg)
wg.Wait()
}
方法二:使用sync.RWMutex
- 原理:
sync.RWMutex
是读写锁,允许多个读操作并发执行,但写操作时会独占锁,不允许其他读写操作。读锁和写锁相互排斥,写锁优先级更高。
- 优点:
- 在读多写少的场景下性能较好,因为读操作可以并发进行,提高了并发读的效率。
- 相比于
sync.Mutex
,减少了读操作之间的竞争。
- 缺点:
- 实现相对复杂一些,需要区分读锁和写锁的使用场景。
- 写操作仍然是独占的,在写操作频繁时,性能提升有限。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
rwmu sync.RWMutex
rwdata = make(map[string]int)
)
func rwrite(key string, value int, wg *sync.WaitGroup) {
defer wg.Done()
rwmu.Lock()
rwdata[key] = value
rwmu.Unlock()
}
func rread(key string, wg *sync.WaitGroup) {
defer wg.Done()
rwmu.RLock()
value := rwdata[key]
fmt.Printf("Read key %s, value %d\n", key, value)
rwmu.RUnlock()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go rwrite("key1", 1, &wg)
go rread("key1", &wg)
wg.Wait()
}
方法三:使用sync.Map
- 原理:
sync.Map
是Go 1.9 引入的专门用于并发场景的map
实现,它内部使用了多个map
和互斥锁来实现高效的并发访问。
- 优点:
- 无需手动加锁,使用方便,直接调用其方法即可安全地进行并发读写操作。
- 性能较高,在高并发场景下,比手动加锁的
map
性能更好,尤其是在读写操作频繁且无规律的情况下。
- 缺点:
- 不支持
len
方法获取元素数量,不能像普通map
一样方便地遍历。
- 相比于普通
map
,内存占用可能会稍高。
- 代码示例:
package main
import (
"fmt"
"sync"
)
func main() {
var sm sync.Map
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
sm.Store("key1", 1)
}()
go func() {
defer wg.Done()
value, ok := sm.Load("key1")
if ok {
fmt.Printf("Read key key1, value %d\n", value)
}
}()
wg.Wait()
}