GO系列
19、GO学习之 同步操作sync包
20、GO学习之 互斥锁、读写锁该如何取舍
文章目录
前言
按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
sync包提供了两种锁,互斥锁(Mutex)和 读写锁(RWMutex),一般有推荐用 互斥锁,它常被用来对结构体对象的内部状态、缓存等进行保护,使用最为广泛。相比之下,读写锁则使用率就不是那么多了,但既然存在,那就有存在的道理和使用场景。
一、互斥锁性能测试
先通过下面一个示例来看一下互斥锁在 cpu = 2 4 8 16 32 的情况下怎么样。
下面的示例中,首先声明一个全局变量 cs 来作为需要保护的临界区数据,声明一个互斥锁对象 mu,测试函数 BenchmarkReadByMutex
中,利用 testing
测试包来执行完成测试,并发执行。
go
package main
import (
"sync"
"testing"
)
// 模拟临界区需要保护的数据
var cs = 0
// 声明互斥锁
var mu sync.Mutex
func BenchmarkReadByMutex(b *testing.B) {
b.RunParallel(func(p *testing.PB) {
for p.Next() {
mu.Lock()
_ = cs
mu.Unlock()
}
})
}
测试结果如下:
bash
PS D:\workspaceGo> go test -bench . .\src\sync\sync_mutex_test.go -cpu 2
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByMutex-2 75139947 15.35 ns/op
PASS
ok command-line-arguments 1.418s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_mutex_test.go -cpu 4
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByMutex-4 48514642 23.85 ns/op
PASS
ok command-line-arguments 1.479s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_mutex_test.go -cpu 8
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByMutex-8 27248883 41.00 ns/op
PASS
ok command-line-arguments 1.435s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_mutex_test.go -cpu 16
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByMutex-16 19349876 61.73 ns/op
PASS
ok command-line-arguments 1.486s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_mutex_test.go -cpu 32
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByMutex-32 18658485 62.07 ns/op
PASS
ok command-line-arguments 1.490s
- BenchmarkReadByMutex 后面的 -2 表示 2个CPU线程同时执行;
- 总共执行了 74433837 次;
- 每个操作耗时 14.02 纳秒;
我们依次执行 CPU 为 2 4 8 16 32 的情况,发现对于互斥锁性能明显在下降。
二、读写锁性能测试
go
package main
import (
"sync"
"testing"
)
// 模拟临界区需要保护的数据
var cs = 0
// 声明读写锁
var mu sync.RWMutex
func BenchmarkReadByRWMutex(b *testing.B) {
b.RunParallel(func(p *testing.PB) {
for p.Next() {
mu.Lock()
_ = cs
mu.Unlock()
}
})
}
func BenchmarkWriteByRWMutex(b *testing.B) {
b.RunParallel(func(p *testing.PB) {
for p.Next() {
mu.Lock()
cs++
mu.Unlock()
}
})
}
测试结果如下:
bash
PS D:\workspaceGo> go test -bench . .\src\sync\sync_rwmutex_test.go -cpu 2
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByRWMutex-2 42862144 26.88 ns/op
BenchmarkWriteByRWMutex-2 40500108 29.53 ns/op
PASS
ok command-line-arguments 4.481s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_rwmutex_test.go -cpu 4
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByRWMutex-4 24739767 43.13 ns/op
BenchmarkWriteByRWMutex-4 24720604 47.31 ns/op
PASS
ok command-line-arguments 2.569s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_rwmutex_test.go -cpu 8
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByRWMutex-8 20657990 54.24 ns/op
BenchmarkWriteByRWMutex-8 20845746 56.85 ns/op
PASS
ok command-line-arguments 2.666s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_rwmutex_test.go -cpu 16
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByRWMutex-16 21274909 57.08 ns/op
BenchmarkWriteByRWMutex-16 19730286 60.09 ns/op
PASS
ok command-line-arguments 3.739s
PS D:\workspaceGo> go test -bench . .\src\sync\sync_rwmutex_test.go -cpu 32
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkReadByRWMutex-32 21293548 56.24 ns/op
BenchmarkWriteByRWMutex-32 18733052 60.76 ns/op
PASS
ok command-line-arguments 3.560s
从测试结果中可以看出,我们依旧执行了 CPU 为 2 4 8 16 32 的情况,发现随着线程数的增加,测试函数 BenchmarkReadByRWMutex
和测试函数 BenchmarkWriteByRWMutex
的性能并没有明细变化太大,从 CPU = 2 到 CPU = 4 的时候比较明显,但是从 CPU = 4 8 16 32 性能则趋于比较平稳。
三、小结
对于互斥锁,CPU 从 2 4 8 16 到 32,随着并发量的增加,性能从 15.35 逐步增大到 62.07 ns/op ,也就是每次操作的时间在增大;
对于读写锁,随着并发量的增加,读锁性能并未随并发量的增大而发生较大的变化,始终保持在 50 左右。
通过对互斥锁(sync.Mutex)和 读写锁(sync.RWMutex)的性能测试结果对比,我们得到如下结论:
- 在并发量较小的情况下,互斥锁 的性能更好,但是随着并发量的逐步增大,互斥锁由于锁竞争激烈,导致加锁和解锁的性能下降;
- 读写锁的 读锁 性能并未锁着并发量的增大而有大的变化;
- 在并发量较大的情况下,读写锁的 加锁 性能比 互斥锁、读写锁的读锁 性能都差,并且随着并发量的增大,写锁 性能有继续下降趋势;
- 读写锁适合在具有一定并发量且 读多写少 的场合。
- 在大量并发下,在多个 goroutine 可以同时持有读写锁的 读锁,从而减少锁竞争中等待时间;
- 在大量并发下,互斥锁即便是 读请求,同一时刻也只能有一个 goroutine 持有锁,其他 goroutine 也只能阻塞在加锁操作上,等待被调度;
现阶段还是对 Go 语言的学习阶段,想必有一些地方考虑的不全面,本文示例全部是亲自手敲代码并且执行通过。
如有问题,还请指教。
评论去告诉我哦!!!一起学习一起进步!!!