浅谈在go语言中的锁

本文涉及的产品
云原生网关 MSE Higress,422元/月
函数计算FC,每月15万CU 3个月
可观测监控 Prometheus 版,每月50GB免费额度
简介: 【5月更文挑战第11天】本文评估了Go标准库`sync`中的`Mutex`和`RWMutex`性能。`Mutex`包含状态`state`和信号量`sema`,不应复制已使用的实例。`Mutex`适用于保护数据,而`RWMutex`在高并发读取场景下更优。测试显示,小并发时`Mutex`性能较好,但随着并发增加,其性能下降;`RWMutex`的读性能稳定,写性能在高并发时低于`Mutex`。

1 标准库 sync 锁的性能评估

jpegPIA25260.2e16d0ba.fill-400x400-c50.jpg

在标准库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得不到执行,将导致全部死锁。

目录
相关文章
|
14天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
57 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
1月前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
41 7
|
1月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
103 71
|
1月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
107 67
|
9天前
|
算法 安全 Go
Go 语言中实现 RSA 加解密、签名验证算法
随着互联网的发展,安全需求日益增长。非对称加密算法RSA成为密码学中的重要代表。本文介绍如何使用Go语言和[forgoer/openssl](https://github.com/forgoer/openssl)库简化RSA加解密操作,包括秘钥生成、加解密及签名验证。该库还支持AES、DES等常用算法,安装简便,代码示例清晰易懂。
40 12
|
1月前
|
存储 Go
go语言中映射
go语言中映射
38 11
|
1月前
|
Go 索引
go语言修改元素
go语言修改元素
34 6
|
12天前
|
监控 算法 安全
解锁企业计算机监控的关键:基于 Go 语言的精准洞察算法
企业计算机监控在数字化浪潮下至关重要,旨在保障信息资产安全与高效运营。利用Go语言的并发编程和系统交互能力,通过进程监控、网络行为分析及应用程序使用记录等手段,实时掌握计算机运行状态。具体实现包括获取进程信息、解析网络数据包、记录应用使用时长等,确保企业信息安全合规,提升工作效率。本文转载自:[VIPShare](https://www.vipshare.com)。
20 0
|
26天前
|
Go 数据安全/隐私保护 UED
优化Go语言中的网络连接:设置代理超时参数
优化Go语言中的网络连接:设置代理超时参数