解决方案代码示例
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
counter := 0
numGoroutines := 10
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
方案解析
- 使用
sync.WaitGroup
同步 goroutine:通过 wg.Add(1)
为每个 goroutine 增加计数,在 goroutine 结束时调用 wg.Done()
减少计数,最后通过 wg.Wait()
阻塞主 goroutine 直到所有计数为0,即所有 goroutine 完成。
- 使用
sync.Mutex
解决资源竞争:在访问和修改共享资源 counter
前,通过 mu.Lock()
加锁,操作完成后通过 mu.Unlock()
解锁,确保同一时间只有一个 goroutine 可以访问和修改共享资源。
性能瓶颈分析
- 锁争用:当有大量 goroutine 频繁竞争锁时,会导致许多 goroutine 处于等待状态,增加上下文切换开销,降低系统整体性能。
- 串行化操作:由于互斥锁的存在,对共享资源的操作变为串行化,无法充分利用多核 CPU 的并行处理能力。
优化方向
- 减少锁粒度:如果共享资源可以进一步细分,可以为每个子资源设置单独的锁,从而减少锁争用。例如,如果计数器是由多个部分组成,可以为每个部分设置锁。
- 读写锁:如果读操作远多于写操作,可以使用
sync.RWMutex
,读操作时允许多个 goroutine 同时进行,写操作时加写锁保证数据一致性。
- 无锁数据结构:使用无锁数据结构,如
sync/atomic
包中的原子操作,对于简单的计数器等场景,可以避免锁的开销。例如:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var wg sync.WaitGroup
var counter uint64
numGoroutines := 10
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddUint64(&counter, 1)
}()
}
wg.Wait()
fmt.Println("Final counter value:", atomic.LoadUint64(&counter))
}
- 使用分布式计算:对于非常高并发的场景,可以将任务分发到多个节点上进行处理,减少单个节点的压力。