策略一:使用sync.RWMutex
进行读写分离
- 原理:如果大部分操作是读操作,写操作较少,可以使用读写锁。读操作可以同时进行,只有写操作需要独占锁,这样可以提高并发性能。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
data int
rwmu sync.RWMutex
)
func read(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.RLock()
fmt.Printf("Read data: %d\n", data)
rwmu.RUnlock()
}
func write(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.Lock()
data++
fmt.Printf("Write data: %d\n", data)
rwmu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go read(&wg)
}
for i := 0; i < 5; i++ {
wg.Add(1)
go write(&wg)
}
wg.Wait()
}
- 潜在问题:如果写操作频繁,读操作也会因为写锁的独占而等待,反而降低性能。同时,读写锁的实现比普通互斥锁复杂,可能会增加维护成本。
策略二:使用分段锁
- 原理:将数据分成多个段,每个段使用一个独立的互斥锁。这样不同段的数据操作可以并发进行,减少锁竞争。
- 代码示例:
package main
import (
"fmt"
"sync"
)
const numSegments = 10
type Segment struct {
data int
mu sync.Mutex
}
type ShardedMutex struct {
segments [numSegments]Segment
}
func (sm *ShardedMutex) getSegmentIndex(key int) int {
return key % numSegments
}
func (sm *ShardedMutex) Lock(key int) {
index := sm.getSegmentIndex(key)
sm.segments[index].mu.Lock()
}
func (sm *ShardedMutex) Unlock(key int) {
index := sm.getSegmentIndex(key)
sm.segments[index].mu.Unlock()
}
func worker(sm *ShardedMutex, key, value int, wg *sync.WaitGroup) {
defer wg.Done()
sm.Lock(key)
sm.segments[sm.getSegmentIndex(key)].data += value
fmt.Printf("Worker with key %d updated data: %d\n", key, sm.segments[sm.getSegmentIndex(key)].data)
sm.Unlock(key)
}
func main() {
var wg sync.WaitGroup
sm := &ShardedMutex{}
for i := 0; i < 20; i++ {
wg.Add(1)
go worker(sm, i, i, &wg)
}
wg.Wait()
}
- 潜在问题:需要合理设计分段逻辑,如果分段不合理,可能仍然会存在较高的锁竞争。同时,增加了代码的复杂性,需要管理多个锁。
策略三:减少锁的粒度
- 原理:尽量缩小锁保护的代码块范围,只在真正需要保护共享资源的代码部分加锁,减少锁的持有时间,从而降低锁竞争。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
data int
mu sync.Mutex
)
func updateData(wg *sync.WaitGroup) {
defer wg.Done()
localData := 0
// 只在读取和更新共享数据时加锁
mu.Lock()
localData = data
localData++
data = localData
mu.Unlock()
fmt.Printf("Updated data: %d\n", data)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go updateData(&wg)
}
wg.Wait()
}
- 潜在问题:缩小锁粒度可能会导致代码逻辑分散,增加代码的维护难度。同时,如果缩小过度,可能无法正确保护共享资源,导致数据竞争问题。