使用 sync.Mutex
- 实现原理:通过
sync.Mutex
对map的读写操作进行加锁保护。在进行读或写操作前,先调用Lock
方法获取锁,操作完成后调用Unlock
方法释放锁。这样可以保证同一时间只有一个goroutine能对map进行操作,从而避免数据竞争。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type SafeMap struct {
mu sync.Mutex
data map[string]interface{}
}
func (sm *SafeMap) Set(key string, value interface{}) {
sm.mu.Lock()
defer sm.mu.Unlock()
if sm.data == nil {
sm.data = make(map[string]interface{})
}
sm.data[key] = value
}
func (sm *SafeMap) Get(key string) (interface{}, bool) {
sm.mu.Lock()
defer sm.mu.Unlock()
if sm.data == nil {
return nil, false
}
value, exists := sm.data[key]
return value, exists
}
- 优点:实现简单直观,对于读写操作频率没有特别限制,能很好地保证并发安全。
- 缺点:性能方面,由于每次读写都要获取锁,在高并发读场景下,会造成大量的等待,影响性能。适用场景为读写操作频率较为均衡的情况。
使用 sync.RWMutex
- 实现原理:
sync.RWMutex
区分了读锁和写锁。多个goroutine可以同时获取读锁进行读操作,只有在写操作时才需要获取写锁,写锁会阻止其他读锁和写锁的获取。这样在读多写少的场景下,可以提高并发性能。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type SafeMapWithRWMutex struct {
mu sync.RWMutex
data map[string]interface{}
}
func (sm *SafeMapWithRWMutex) Set(key string, value interface{}) {
sm.mu.Lock()
defer sm.mu.Unlock()
if sm.data == nil {
sm.data = make(map[string]interface{})
}
sm.data[key] = value
}
func (sm *SafeMapWithRWMutex) Get(key string) (interface{}, bool) {
sm.mu.RLock()
defer sm.mu.RUnlock()
if sm.data == nil {
return nil, false
}
value, exists := sm.data[key]
return value, exists
}
- 优点:在高并发读、低并发写的场景下,性能比
sync.Mutex
有显著提升,因为读操作可以并发执行。
- 缺点:实现相对复杂一些,需要区分读锁和写锁的使用。如果写操作频繁,写锁会阻塞读操作,影响整体性能。适用于读多写少的场景。
使用 channel
- 实现原理:将对map的操作封装成消息,通过channel发送给一个专门处理这些消息的goroutine。这个goroutine按照顺序依次处理接收到的消息,从而保证对map操作的原子性。
- 示例代码:
package main
import (
"fmt"
)
type OpType int
const (
Set OpType = iota
Get
)
type Op struct {
opType OpType
key string
value interface{}
result chan interface{}
}
func NewSafeMap() chan Op {
data := make(map[string]interface{})
ch := make(chan Op)
go func() {
for op := range ch {
switch op.opType {
case Set:
data[op.key] = op.value
case Get:
value, exists := data[op.key]
if exists {
op.result <- value
} else {
op.result <- nil
}
}
}
}()
return ch
}
- 优点:不需要显式使用锁,通过channel的特性实现并发安全,代码结构相对清晰。在某些场景下,如分布式系统中通过消息传递来操作数据,这种方式更符合设计理念。
- 缺点:性能方面,由于所有操作都要通过channel传递,会有一定的额外开销。适用场景为需要通过消息传递进行数据操作,并且对性能要求不是极致高的场景。