1 标准库 sync 锁的性能评估
在标准库Mutex的定义非常简单,它有两个字段 state,sema组成:
type Mutex struct {
state int32
sema uint32
}
这两个字段表示
state 表示当前互斥锁状态。
sema 用于控制锁状态信号量。
sync同步包 在 src/sync/ 路径,在其中有这样的提示:
不应该复制哪些包含了此包中类型的值。
禁止复制首次使用后的Mutex
禁止复制使用后的RWMutex
禁止复制使用后的Cond
对mutex实例的复制即是对两个整型字段的复制。
在初始状态,Mutex实例处于 Unlocked状态,state和sema都为0.
实例副本state字段值也为 sync.mutexLocked ,
因此在对其实例复制的副本调用Lock将导致进入阻塞 。
--- 也就是死锁 因为没有任何其他计划调用该副本的Unlock方法,Go不支持递归锁---
那些sync包中类型的实例在首次使用后被复制得到的副本,一旦再被使用将导致不可预期结果,为此在使用sync包的类型时,
推荐通过闭包方式或传递类型实例(或包裹该类型的类型实例)的地址或指针进行,这是sync包最需要注意的。
互斥锁 sync.Mutex,也是编程的同步原语首选,常被用来对结构体对象内部状态,缓存进行保护。 使用最为广泛。
它通常被用以保护结构体内部状态,缓存,是广泛使用的同步原语。
读写锁 RWMutex 有大并发需求的创建,使用读写锁。 RWMutex。
读写锁适合具有一定并发量,并且读取操作明显大于写操作的场景。
2 互斥锁和读写锁例子
一个简单官方例子如下:
创建 锁需要保护的数据变量
var ( dataOne = 0 dataTwo = 1 mutexOne sync.Mutex mutexTwo sync.RWMutex )
互斥锁 读取性能
func BenchmarkReadSyncByMutex(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { mutexOne.Lock() _ = dataOne mutexOne.Unlock() } }) }
互斥锁 写入性能
func BenchmarkWriteSyncByMutex(b testing.B) {
b.RunParallel(func(pb testing.PB) {for pb.Next() { mutexOne.Lock() dataOne += 1 mutexOne.Unlock() }
})
}
读写锁 读取性能评估
func BenchmarkReadSyncByRWMutex(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { mutexTwo.Lock() _ = dataTwo mutexTwo.Unlock() } }) }
读写锁 写性能评估
func BenchmarkWriteSyncByRWMutex(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { mutexTwo.Lock() dataTwo += 1 mutexTwo.Unlock() } }) }
执行:
go test -v -count 2 -bench . mutex_rw_bench_test.go -cpu 2,4,8,32,128 >bm.txt
结果查看:
goarch: amd64 cpu: AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx BenchmarkReadSyncByMutex BenchmarkReadSyncByMutex-2 30831019 40.23 ns/op BenchmarkReadSyncByMutex-2 32428663 42.62 ns/op BenchmarkReadSyncByMutex-4 10713606 114.1 ns/op BenchmarkReadSyncByMutex-4 10344114 98.16 ns/op BenchmarkReadSyncByMutex-8 10293854 116.5 ns/op BenchmarkReadSyncByMutex-8 10168749 116.9 ns/op BenchmarkReadSyncByMutex-32 11110328 111.3 ns/op BenchmarkReadSyncByMutex-32 10753728 108.3 ns/op BenchmarkReadSyncByMutex-128 12562038 98.00 ns/op BenchmarkReadSyncByMutex-128 12499010 96.89 ns/op BenchmarkWriteSyncByMutex BenchmarkWriteSyncByMutex-2 17350693 67.81 ns/op BenchmarkWriteSyncByMutex-2 15188412 66.77 ns/op BenchmarkWriteSyncByMutex-4 9374296 125.0 ns/op BenchmarkWriteSyncByMutex-4 10168714 126.8 ns/op BenchmarkWriteSyncByMutex-8 9916609 119.1 ns/op BenchmarkWriteSyncByMutex-8 9755517 121.1 ns/op BenchmarkWriteSyncByMutex-32 10713538 113.9 ns/op BenchmarkWriteSyncByMutex-32 10568701 113.5 ns/op BenchmarkWriteSyncByMutex-128 11649591 102.3 ns/op BenchmarkWriteSyncByMutex-128 11973096 102.5 ns/op BenchmarkReadSyncByRWMutex BenchmarkReadSyncByRWMutex-2 13524128 102.7 ns/op BenchmarkReadSyncByRWMutex-2 11999124 101.4 ns/op BenchmarkReadSyncByRWMutex-4 8391038 145.8 ns/op BenchmarkReadSyncByRWMutex-4 14412699 126.1 ns/op BenchmarkReadSyncByRWMutex-8 10525567 116.3 ns/op BenchmarkReadSyncByRWMutex-8 10255752 116.4 ns/op BenchmarkReadSyncByRWMutex-32 10255778 117.3 ns/op BenchmarkReadSyncByRWMutex-32 10208638 117.9 ns/op BenchmarkReadSyncByRWMutex-128 10810089 111.0 ns/op BenchmarkReadSyncByRWMutex-128 11110348 108.1 ns/op BenchmarkWriteSyncByRWMutex BenchmarkWriteSyncByRWMutex-2 12499010 91.11 ns/op BenchmarkWriteSyncByRWMutex-2 11999124 99.52 ns/op BenchmarkWriteSyncByRWMutex-4 7842598 147.7 ns/op BenchmarkWriteSyncByRWMutex-4 7946450 151.0 ns/op BenchmarkWriteSyncByRWMutex-8 10210080 118.1 ns/op BenchmarkWriteSyncByRWMutex-8 10168724 115.7 ns/op BenchmarkWriteSyncByRWMutex-32 9835380 119.9 ns/op BenchmarkWriteSyncByRWMutex-32 10339772 117.5 ns/op BenchmarkWriteSyncByRWMutex-128 10908296 109.5 ns/op BenchmarkWriteSyncByRWMutex-128 10810030 109.9 ns/op PASS
3 小结
简单分析如下:
1 在小并发量时,互斥锁性能更好,并发量增大,互斥锁竞争激烈,导致加锁和解锁性能下降,
但是最后也恒定在最好记录的2倍左右。
2 读写锁的读锁性能并未随着并发量增大而性能下降,始终在恒定值.
3 并发量较大时,读写锁的写锁性能比互斥锁,读写锁的读锁都差,并且随着并发量增大,写锁性能有继续下降趋势。
多个例程goroutine可以同时持有读锁,从而减少在锁竞争等待的时间,
而互斥锁即便为读请求,同一时刻也只能有一个例程持有锁,其他goroutine被阻塞在加锁操作等待被调度。
由于处于for循环测试中,需要注意的是,不能在 unlock时使用 defer,
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mutexTwo.Lock()
dataTwo += 1
defer mutexTwo.Unlock()
}
})
如此在并发执行时,函数不会退出,defer得不到执行,将导致全部死锁。