可能导致性能瓶颈的原因
- 锁争用:高并发时多个 goroutine 频繁竞争同一把 Mutex 锁,导致大量 goroutine 处于等待状态,增加了上下文切换开销。
- 串行化执行:获得锁的 goroutine 在临界区执行时,其他 goroutine 必须等待,限制了并行性,在临界区执行时间较长时影响较大。
优化方案及适用场景
- 读写锁(RWMutex)
- 适用场景:当读操作远多于写操作时。例如在缓存系统中,大量请求读取缓存数据,偶尔更新缓存。读操作可并发执行,写操作则独占锁,保证数据一致性。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var (
data int
rwmu sync.RWMutex
)
func read(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.RLock()
defer rwmu.RUnlock()
fmt.Println("read data:", data)
}
func write(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.Lock()
defer rwmu.Unlock()
data++
fmt.Println("write data:", data)
}
- 分段锁
- 适用场景:数据可按一定规则划分成多个独立部分,且不同部分的操作可并行进行。如在数据库分表场景下,对不同表的操作可使用不同的锁。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type ShardedMutex struct {
shards []sync.Mutex
numShards int
}
func NewShardedMutex(numShards int) *ShardedMutex {
shards := make([]sync.Mutex, numShards)
return &ShardedMutex{
shards: shards,
numShards: numShards,
}
}
func (sm *ShardedMutex) Lock(key int) {
index := key % sm.numShards
sm.shards[index].Lock()
}
func (sm *ShardedMutex) Unlock(key int) {
index := key % sm.numShards
sm.shards[index].Unlock()
}
- 减少锁粒度
- 适用场景:当临界区代码可进一步细分,不同部分的操作不相互影响时。例如在一个复杂的数据结构操作中,可对不同字段分别加锁。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type Data struct {
field1 int
field2 int
mu1 sync.Mutex
mu2 sync.Mutex
}
func updateField1(d *Data, wg *sync.WaitGroup) {
defer wg.Done()
d.mu1.Lock()
defer d.mu1.Unlock()
d.field1++
fmt.Println("update field1:", d.field1)
}
func updateField2(d *Data, wg *sync.WaitGroup) {
defer wg.Done()
d.mu2.Lock()
defer d.mu2.Unlock()
d.field2++
fmt.Println("update field2:", d.field2)
}