GO学习之 互斥锁、读写锁该如何取舍

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
13、GO学习之 数据库(Redis)
14、GO学习之 搜索引擎(ElasticSearch)
15、GO学习之 消息队列(Kafka)
16、GO学习之 远程过程调用(RPC)
17、GO学习之 goroutine的调度原理
18、GO学习之 通道(nil Channel妙用)
19、GO学习之 同步操作sync包
20、GO学习之 互斥锁、读写锁该如何取舍
21、GO学习之 条件变量 sync.Cond

文章目录

  • GO系列
  • 前言
  • 一、互斥锁性能测试
  • 二、读写锁性能测试
  • 三、小结

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
sync包提供了两种锁,互斥锁(Mutex)和 读写锁(RWMutex),一般有推荐用 互斥锁,它常被用来对结构体对象的内部状态、缓存等进行保护,使用最为广泛。相比之下,读写锁则使用率就不是那么多了,但既然存在,那就有存在的道理和使用场景。

一、互斥锁性能测试

先通过下面一个示例来看一下互斥锁在 cpu = 2 4 8 16 32 的情况下怎么样。
下面的示例中,首先声明一个全局变量 cs 来作为需要保护的临界区数据,声明一个互斥锁对象 mu,测试函数 BenchmarkReadByMutex 中,利用 testing 测试包来执行完成测试,并发执行。

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()
		}
	})
}

测试结果如下:

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 的情况,发现对于互斥锁性能明显在下降。

二、读写锁性能测试

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()
		}
	})
}

测试结果如下:

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 语言的学习阶段,想必有一些地方考虑的不全面,本文示例全部是亲自手敲代码并且执行通过。
如有问题,还请指教。
评论去告诉我哦!!!一起学习一起进步!!!

你可能感兴趣的:(golang学习,golang,开发语言)