锁的基础
原子(atomic)操作
- 原子操作是一种硬件层面加锁的机制
- 保证操作一个变量的时候,其他协程/线程无法访问
- 只能适用于简单变量的简单操作 ```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) }
<a name="LhVmx"></a>## sema锁1. 也叫信号量锁/信号锁2. 核心是uint32值,含义是同时可并发的数量3. 每一个sema锁都对应一个semaRoot结构体<a name="nP5Oc"></a>### sema操作(sema>0)1. 获取锁:uint32减一,获取成功2. 释放锁:uint32加一,释放成功<a name="FxKU2"></a>### sema==01. 获取锁:协程休眠。进入堆树等待2. 释放锁:从堆树中取出一个协程,唤醒3. sema锁退化成一个专用休眠队列<a name="UzB5q"></a>## 总结1. 原子操作是一种硬件层面加锁的机制2. 数据类型和操作类型都有限制3. sema锁是runtime的常用工具4. sema锁经常被用作休眠队列<a name="fHoC0"></a># 互斥锁1. sync.Mutex2. go 用于并发保护最常见的方案<a name="O1w7u"></a>## 结构体```gotype Mutex struct {state int32sema uint32}
正常模式
加锁
- 尝试CAS直接加锁
- 若无法直接获取,进行多次自旋尝试
- 多次尝试失败,进入sema队列休眠
解锁
- 将locked置为1的协程,会把locked置为0. 然后检查WaiterShift有没有协程在等待

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

- 被唤醒的协程,继续去抢夺locked这把锁,有可能还会抢不到这把锁,会继续进入队列休眠.重复此操作会造成锁饥饿
总结
- mutex正常模式: 自旋加锁+sema休眠队列
-
锁饥饿
总结
锁竞争严重时,互斥锁进入饥饿模式
饥饿模式没有自旋等待,有利于公平(饥饿模式,新进的协程直接进入队里休眠)
经验
要减少锁的使用时间
-
读写锁
结构体
type RWMutex struct {w Mutex // held if there are pending writerswriterSem uint32 // semaphore for writers to wait for completing readersreaderSem uint32 // semaphore for readers to wait for completing writersreaderCount int32 // 已经架设读锁协程的数量readerWait int32 // number of departing readers}
w:互斥锁作为写锁
- writerSem:写协程队列
- readerSem:读协程队列
- readerCount:正值:正在读的协程,负值:加了写锁的数量
- readerWait:写锁应该等待释放读协程的个数
等待组waiteGroup
结构体
type WaitGroup struct {noCopy noCopy //告诉编译器,该结构体禁止拷贝// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.// 64-bit atomic operations require 64-bit alignment, but 32-bit// compilers do not ensure it. So we allocate 12 bytes and then use// the aligned 8 bytes in them as state, and the other 4 as storage// for the sema.// 三个uint32, 一个为被等待协程计数器counter,一个为等待协程计数器waiter count,一个为seam等待队列state1 [3]uint32}
package mainimport ("fmt""sync")type Person struct {salary intlevel int}func (p *Person) promote( w *sync.WaitGroup) {p.salary+=1000fmt.Println("salary",p.salary)p.level++fmt.Println("level",p.level)w.Done()}func main() {P:=Person{level: 1,salary: 10000}wg := sync.WaitGroup{}wg.Add(3)go P.promote(&wg)go P.promote(&wg)go P.promote(&wg)wg.Wait()}

once锁
结构体
type Once struct {// done indicates whether the action has been performed.// It is first in the struct because it is used in the hot path.// The hot path is inlined at every call site.// Placing done first allows more compact instructions on some architectures (amd64/386),// and fewer instructions (to calculate offset) on other architectures.done uint32m Mutex}


排查锁异常情况
vet工具

package mainimport ("fmt""sync")type Person struct {mu sync.RWMutexsalary intlevel int}func (p *Person) promote( ) {//p.mu.Lock()//defer p.mu.Unlock()p.salary+=1000fmt.Println("salary",p.salary)p.level++fmt.Println("level",p.level)}func main() {// 拷贝场景一P:=Person{level: 1,salary: 10000}p:=P//拷贝的结构体中有互斥锁// 拷贝场景二m:=sync.Mutex{}m.Lock()//业务代码n:=m//拷贝锁//业务代码m.Unlock()n.Lock()}
go vet main.go
数据竞争

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

https://github.com/sasha-s/go-deadlock
总结











