浅谈在go语言中的锁

本文涉及的产品
云原生网关 MSE Higress,422元/月
性能测试 PTS,5000VUM额度
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 【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得不到执行,将导致全部死锁。

目录
相关文章
|
2天前
|
存储 Go
go语言 遍历映射(map)
go语言 遍历映射(map)
10 2
|
3天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
3天前
|
测试技术 Go 索引
go语言使用 range 关键字遍历
go语言使用 range 关键字遍历
14 3
|
3天前
|
测试技术 Go 索引
go语言通过 for 循环遍历
go语言通过 for 循环遍历
13 3
|
5天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
4天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
16 3
|
8天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
29 4
|
8天前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
40 1
|
11天前
|
Go
go语言中的continue 语句
go语言中的continue 语句
24 3
|
6天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
18 0