image.png

锁的基础

原子(atomic)操作

  1. 原子操作是一种硬件层面加锁的机制
  2. 保证操作一个变量的时候,其他协程/线程无法访问
  3. 只能适用于简单变量的简单操作 ```go

func do(p int32) { // p相加无法达到1000 p++ // 采用原子操作 atomic.AddInt32(p,1) }

func main() { p := int32(0) for i := 0; i < 1000; i++ { go do(&p) } time.Sleep(time.Second) fmt.Println(p) }

  1. <a name="LhVmx"></a>
  2. ## sema锁
  3. 1. 也叫信号量锁/信号锁
  4. 2. 核心是uint32值,含义是同时可并发的数量
  5. 3. 每一个sema锁都对应一个semaRoot结构体
  6. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22399957/1663903555187-ff2531bd-a099-4a3f-be03-df5f2142b286.png#clientId=u5fab819e-4140-4&errorMessage=unknown%20error&from=paste&height=864&id=u7dbb9ff0&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=220024&status=error&style=none&taskId=u5bd7f7b5-35e2-4faf-8a85-25cd9b5b16b&title=&width=1536)
  7. <a name="nP5Oc"></a>
  8. ### sema操作(sema>0)
  9. 1. 获取锁:uint32减一,获取成功
  10. 2. 释放锁:uint32加一,释放成功
  11. <a name="FxKU2"></a>
  12. ### sema==0
  13. 1. 获取锁:协程休眠。进入堆树等待
  14. 2. 释放锁:从堆树中取出一个协程,唤醒
  15. 3. sema锁退化成一个专用休眠队列
  16. <a name="UzB5q"></a>
  17. ## 总结
  18. 1. 原子操作是一种硬件层面加锁的机制
  19. 2. 数据类型和操作类型都有限制
  20. 3. sema锁是runtime的常用工具
  21. 4. sema锁经常被用作休眠队列
  22. <a name="fHoC0"></a>
  23. # 互斥锁
  24. 1. sync.Mutex
  25. 2. go 用于并发保护最常见的方案
  26. <a name="O1w7u"></a>
  27. ## 结构体
  28. ```go
  29. type Mutex struct {
  30. state int32
  31. sema uint32
  32. }

image.png

正常模式

加锁

  1. 尝试CAS直接加锁
  2. 若无法直接获取,进行多次自旋尝试
  3. 多次尝试失败,进入sema队列休眠

右侧的G已经将locked置为1,左侧的G只能自旋多次尝试解开锁,多次尝试失败后,会去寻找sema,次时sema=0,则为休眠队列,然后右侧的G就会放置到treap下面的平衡二叉树中,等待被唤醒。state的WaiterShift加一,表示等待的休眠协程数量加一

解锁

  1. 将locked置为1的协程,会把locked置为0. 然后检查WaiterShift有没有协程在等待

image.png

  1. 有协程等待,会唤醒sema中的一个协程放入到调度器中

image.png

  1. 被唤醒的协程,继续去抢夺locked这把锁,有可能还会抢不到这把锁,会继续进入队列休眠.重复此操作会造成锁饥饿

image.png

总结

  1. mutex正常模式: 自旋加锁+sema休眠队列
  2. mutex正常模式下.,可能会造成锁饥饿的问

    锁饥饿

    image.png
    进入饥饿模式后,直到sema休眠队列中没有就休眠的协程,才会结束饥饿模式

    总结

  3. 锁竞争严重时,互斥锁进入饥饿模式

  4. 饥饿模式没有自旋等待,有利于公平(饥饿模式,新进的协程直接进入队里休眠)

    经验

  5. 要减少锁的使用时间

  6. 善用defer确保锁的释放

    读写锁

    image.png
    image.png

    结构体

    1. type RWMutex struct {
    2. w Mutex // held if there are pending writers
    3. writerSem uint32 // semaphore for writers to wait for completing readers
    4. readerSem uint32 // semaphore for readers to wait for completing writers
    5. readerCount int32 // 已经架设读锁协程的数量
    6. readerWait int32 // number of departing readers
    7. }
  7. w:互斥锁作为写锁

  8. writerSem:写协程队列
  9. readerSem:读协程队列
  10. readerCount:正值:正在读的协程,负值:加了写锁的数量
  11. readerWait:写锁应该等待释放读协程的个数

image.png
image.png
image.png
image.png
image.png
image.png

等待组waiteGroup

结构体

  1. type WaitGroup struct {
  2. noCopy noCopy //告诉编译器,该结构体禁止拷贝
  3. // 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
  4. // 64-bit atomic operations require 64-bit alignment, but 32-bit
  5. // compilers do not ensure it. So we allocate 12 bytes and then use
  6. // the aligned 8 bytes in them as state, and the other 4 as storage
  7. // for the sema.
  8. // 三个uint32, 一个为被等待协程计数器counter,一个为等待协程计数器waiter count,一个为seam等待队列
  9. state1 [3]uint32
  10. }
  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type Person struct {
  7. salary int
  8. level int
  9. }
  10. func (p *Person) promote( w *sync.WaitGroup) {
  11. p.salary+=1000
  12. fmt.Println("salary",p.salary)
  13. p.level++
  14. fmt.Println("level",p.level)
  15. w.Done()
  16. }
  17. func main() {
  18. P:=Person{level: 1,salary: 10000}
  19. wg := sync.WaitGroup{}
  20. wg.Add(3)
  21. go P.promote(&wg)
  22. go P.promote(&wg)
  23. go P.promote(&wg)
  24. wg.Wait()
  25. }

image.png

image.png
image.png
image.png
image.png

once锁

结构体

  1. type Once struct {
  2. // done indicates whether the action has been performed.
  3. // It is first in the struct because it is used in the hot path.
  4. // The hot path is inlined at every call site.
  5. // Placing done first allows more compact instructions on some architectures (amd64/386),
  6. // and fewer instructions (to calculate offset) on other architectures.
  7. done uint32
  8. m Mutex
  9. }

image.png
image.png

排查锁异常情况

vet工具

image.png

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type Person struct {
  7. mu sync.RWMutex
  8. salary int
  9. level int
  10. }
  11. func (p *Person) promote( ) {
  12. //p.mu.Lock()
  13. //defer p.mu.Unlock()
  14. p.salary+=1000
  15. fmt.Println("salary",p.salary)
  16. p.level++
  17. fmt.Println("level",p.level)
  18. }
  19. func main() {
  20. // 拷贝场景一
  21. P:=Person{level: 1,salary: 10000}
  22. p:=P//拷贝的结构体中有互斥锁
  23. // 拷贝场景二
  24. m:=sync.Mutex{}
  25. m.Lock()
  26. //业务代码
  27. n:=m//拷贝锁
  28. //业务代码
  29. m.Unlock()
  30. n.Lock()
  31. }
  1. go vet main.go

数据竞争

image.png

  1. go build -race main.go
  2. ./main.exe //执行编译后的文件,就可以查看到代码文件中的数据竞争或者bug
  1. func main() {
  2. n:=0
  3. for i := 0; i < 200; i++ {
  4. go func() {
  5. n++
  6. }()
  7. }
  8. fmt.Println(n)//n无法到达200
  9. time.Sleep(time.Second * 10)
  10. }

死锁检测

image.png

  1. https://github.com/sasha-s/go-deadlock

总结

image.png