- 减少锁的粒度
- 方法说明:将大的共享资源拆分成多个小的部分,每个部分使用单独的锁进行保护。这样不同的操作可以同时访问不同部分的资源,减少锁竞争。
- 实际场景举例:在一个订单处理系统中,订单数据可能包含订单基本信息、商品列表、客户信息等。如果使用一个大锁保护整个订单数据,在处理订单基本信息更新时,商品列表和客户信息的操作也会被阻塞。可以分别为订单基本信息、商品列表、客户信息设置单独的锁,比如更新订单基本信息时只锁定基本信息对应的锁,商品列表的操作可以同时进行,互不影响。
package main
import (
"fmt"
"sync"
)
type Order struct {
baseInfo string
items []string
customer string
baseMutex sync.Mutex
itemMutex sync.Mutex
custMutex sync.Mutex
}
func (o *Order) updateBaseInfo(newInfo string) {
o.baseMutex.Lock()
o.baseInfo = newInfo
o.baseMutex.Unlock()
}
func (o *Order) addItem(newItem string) {
o.itemMutex.Lock()
o.items = append(o.items, newItem)
o.itemMutex.Unlock()
}
- 读写锁的使用(
sync.RWMutex
)
- 方法说明:如果共享资源的读操作远远多于写操作,可以使用读写锁。读写锁允许多个读操作同时进行,但写操作时会独占锁,不允许其他读写操作。
- 实际场景举例:在一个新闻发布系统中,新闻内容会被大量用户读取,但只有管理员会进行更新操作。使用读写锁,用户读取新闻时获取读锁,多个用户可以同时读取。当管理员更新新闻时获取写锁,此时其他读写操作都被阻塞。
package main
import (
"fmt"
"sync"
)
type News struct {
content string
rwMutex sync.RWMutex
}
func (n *News) readNews() string {
n.rwMutex.RLock()
defer n.rwMutex.RUnlock()
return n.content
}
func (n *News) updateNews(newContent string) {
n.rwMutex.Lock()
n.content = newContent
n.rwMutex.Unlock()
}
- 无锁数据结构
- 方法说明:使用Go语言提供的一些无锁数据结构,如
sync.Map
,它内部实现了无锁的并发安全的键值对存储,避免了使用锁带来的性能开销。
- 实际场景举例:在一个分布式缓存系统中,需要频繁地进行缓存的读写操作。使用
sync.Map
可以高效地处理并发读写,不需要手动加锁。
package main
import (
"fmt"
"sync"
)
func main() {
var cache sync.Map
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id)
value := fmt.Sprintf("value%d", id)
cache.Store(key, value)
result, ok := cache.Load(key)
if ok {
fmt.Printf("goroutine %d read: %v\n", id, result)
}
}(i)
}
wg.Wait()
}
- 分段锁(类似
sync.Map
的实现思路)
- 方法说明:将共享资源分成多个段,每个段使用一个独立的锁。根据数据的某些特征(如哈希值)决定使用哪个段的锁,从而减少锁竞争。
- 实际场景举例:在一个大规模用户信息管理系统中,用户ID是唯一标识。可以根据用户ID的哈希值将用户信息分配到不同的段,每个段有自己的锁。当更新或查询用户信息时,根据用户ID计算哈希值确定使用哪个段的锁,不同段的操作可以并发进行。
package main
import (
"fmt"
"hash/fnv"
"sync"
)
const numSegments = 10
type User struct {
name string
}
type UserManager struct {
segments []sync.Mutex
users [numSegments]map[uint32]*User
}
func NewUserManager() *UserManager {
um := &UserManager{}
for i := range um.segments {
um.segments = append(um.segments, sync.Mutex{})
um.users[i] = make(map[uint32]*User)
}
return um
}
func (um *UserManager) hashKey(key uint32) int {
return int(key % numSegments)
}
func (um *UserManager) addUser(key uint32, user *User) {
index := um.hashKey(key)
um.segments[index].Lock()
um.users[index][key] = user
um.segments[index].Unlock()
}
func (um *UserManager) getUser(key uint32) *User {
index := um.hashKey(key)
um.segments[index].Lock()
user := um.users[index][key]
um.segments[index].Unlock()
return user
}