锁基础
atomic(原子操作)
是硬件层面加锁的机制,是最底层的方法
函数传指针才能对其++操作,否则修改的值无影响
sema锁(go锁底层),底下有个semaroot结构体
信号锁 uint类型
uint == 0 协程会加入到底层的平衡二叉树中执行gopark()挂起其他协程释放锁时会拿出执行
sema == 0 的时候会被当作普通的等待队列使用(极少当作锁来使用)
获取锁uint-1 释放锁uint+1
互斥锁
结构体的方法实现用指针才能修改成员的值
在结构体中用mu sysn.Mutex
方法中先Lock()后UnLock()
Mutex
sema
state 是否加锁,唤醒,饥饿,WaiterShift(竞争锁失败后在堆树休眠的协程个数)
得到锁就可以做业务了,别人就拿不到这把锁了
竞争Locked,得不到的会多次自旋操作(执行空语句)
Lock解开后唤醒堆树中一个协程
spin自旋
饥饿模式
当前协程等待锁时间超过1s,进入饥饿模式
该模式中,不自旋,新来的协程获取不到Lock直接sema休眠
被唤醒的协程直接获取锁
没有协程在sema中回到正常模式
用defer处理锁比较好,防止panic后锁得不到释放
读写锁
RWMutex
读取的过程希望加锁,只加RMutex就行
(多个协程并发只读)
读写锁原理
读锁为共享锁,可以上许多个
写锁在存在读锁的时刻是无法获取的
写协程放到等待队列中,获取后取出
已加读锁的情况下无法加写锁,读协程放到等待队列中
RWMutex底层
w 互斥锁作为写锁
读,写sema (两个协程队列)
readerCount 正值(正在读的协程)负值(加了写锁)
readerWait 写锁应该等待读协程的个数
读多写少的场景用RW锁带来的性能优势较高
WG
组协程等待
底层 state3个uint成员数组(被等待协程,等待协程(放在sema中),sema队列)
race调试用的信息
wait()
等待协程+1 Add,Done()被等待协程-1
段代码
只执行一次
go once.Do(func)
思路1
CAS(0 ,1) 成功就做,失败就不做,算法简单,但大量协程竞争一个资源造成性能问题
实际
互斥锁Mutex Done(int32)
判断标志位Done
获取Mutex,改值解锁唤醒协程,返回
不论业务代码执行是否正常,都会用defer设置标志位,并解锁。后面的协程在看到标志位后不会再次执行。
多个 goroutine 同时执行 once.Do 的时候,可以保证抢占到 once.Do 执行权的 goroutine 执行完 once.Do 后,其他 goroutine 才能得到返回
锁异常
go vet 查看是否存在拷贝锁
race 竞争检测
go build - race
升值加薪不会到20次的
RW 锁中有 3 个 sema,分别为 1.Mutex 中的 sema 2.writerSem 3.readerSem