互斥锁饥饿问题产生的原因
- 高竞争场景:在高并发环境下,大量goroutine同时竞争互斥锁,导致获取锁的等待队列不断增长。
- 公平性问题:Go的互斥锁默认是非公平的。当一个锁被释放时,新唤醒的goroutine和刚刚被阻塞的goroutine竞争,新唤醒的goroutine有更高的机会获得锁,这就可能导致长时间等待在队列中的goroutine一直得不到锁,从而产生饥饿。
优化策略
- 使用公平锁
- 原理:公平锁保证等待时间最长的goroutine优先获取锁,避免新唤醒的goroutine总是优先获得锁的情况,从而解决饥饿问题。
- 具体实现方式:Go标准库的
sync.Mutex
默认是非公平的,但可以通过在获取锁和释放锁时添加逻辑来实现公平锁。例如:
package main
import (
"context"
"fmt"
"sync"
"time"
)
type FairMutex struct {
mu sync.Mutex
waiters int
waiting chan struct{}
released bool
}
func NewFairMutex() *FairMutex {
return &FairMutex{
waiting: make(chan struct{}, 1),
}
}
func (fm *FairMutex) Lock(ctx context.Context) {
fm.mu.Lock()
if fm.waiters == 0 {
fm.waiting <- struct{}{}
}
fm.waiters++
fm.mu.Unlock()
select {
case <-ctx.Done():
fm.mu.Lock()
fm.waiters--
if fm.waiters == 0 {
<-fm.waiting
}
fm.mu.Unlock()
return
case <-fm.waiting:
fm.mu.Lock()
fm.waiters--
if fm.waiters == 0 {
fm.released = true
}
fm.mu.Unlock()
}
}
func (fm *FairMutex) Unlock() {
fm.mu.Lock()
if fm.waiters > 0 {
if!fm.released {
fm.released = true
fm.waiting <- struct{}{}
}
} else {
fm.released = false
<-fm.waiting
}
fm.mu.Unlock()
}
- 减少锁的持有时间
- 原理:减少锁的持有时间,可以降低其他goroutine等待的时间,从而减少饥饿发生的概率。
- 具体实现方式:将需要锁保护的代码尽量精简,只在真正需要保护共享资源的代码段加锁。例如:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var data int
func worker(wg *sync.WaitGroup) {
// 只在访问共享资源时加锁
mu.Lock()
localData := data
mu.Unlock()
// 长时间运行的计算,不影响锁的释放
result := localData * localData
fmt.Println("Result:", result)
wg.Done()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
}
- 使用读写锁(适用于读多写少场景)
- 原理:读写锁(
sync.RWMutex
)允许多个读操作同时进行,只有写操作需要独占锁。在高并发读多写少的场景下,使用读写锁可以减少读操作等待锁的时间,降低饥饿风险。
- 具体实现方式:
package main
import (
"fmt"
"sync"
)
var rwmu sync.RWMutex
var sharedData int
func reader(wg *sync.WaitGroup) {
rwmu.RLock()
fmt.Println("Reader read data:", sharedData)
rwmu.RUnlock()
wg.Done()
}
func writer(wg *sync.WaitGroup) {
rwmu.Lock()
sharedData++
fmt.Println("Writer updated data:", sharedData)
rwmu.Unlock()
wg.Done()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go reader(&wg)
}
for i := 0; i < 2; i++ {
wg.Add(1)
go writer(&wg)
}
wg.Wait()
}