知识储备
临界区
在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。
CAS
- CAS 指令将给定的值和一个内存地址中的值进行比较,如果它们是同一个值,就使用新值替换内存地址中的值,这个操作是原子性的
- 原子性保证这个指令总是基于最新的值进行计算,如果同时有其它线程已经修改了这个值,那么,CAS 会返回失败。
sync
mutex
当一个 goroutine 获得锁后, 其它请求锁的goroutine 就会阻塞,直到锁被释放并且自己获得这个锁
Locker 接口
mutex实现了Locker接口
四个阶段
- 初版
- 给新人机会
- 给给些机会
- 解决饥饿
结构体
- state
- mutexLocked 持有锁标记
- mutexWoken 唤醒标记
- mutexStarving 饥饿标记
- mutexWaiters 阻塞等待的waiter数量
- sema:信号量变量,用来控制等待goroutine的阻塞休眠和唤醒
模式
正常模式
- 先入先出,被唤醒的 waiter 和新来的 goroutine 进行竞争
- 唤醒的waiter获取不到锁,插入到队列的前面。新来的 goroutine获取不到锁,插入到队列尾部
- waiter获取不到锁超过1毫秒,进入饥饿模式
饥饿模式
- 锁交给队列最前面的waiter
- 新来的goroutine不会获取锁,直接插入队列尾部
- waiter是队列中的最后一个了或者waiter的等待时间小于 1 毫秒,进入正常模式
易错场景
- Lock/Unlock 不是成对出现
- Copy 已使用的 Mutex
- 重入
- 死锁
- 互斥
- 持有和等待
- 不可剥夺
- 环路等待
RWMutex
某一时刻只能由任意数量的 reader 持有,或者是只被单个的 writer 持有
reader和writer优先级
- 写优先
- 请求的writer到来,如果已经有一些reader请求了锁的话,writer会等待已经存在的reader都释放锁之后才获取到锁,后面来的新reader要等writer执行完后才会获得锁 。
结构体
- 5个字段
- w Mutex // 互斥锁解决多个writer的竞争
- writerSem uint32 // writer信号量
- readerSem uint32 // reader信号量
- readerCount int32 // 记录当前 reader 的数量(以及是否有 writer 竞争锁)
- readerWait int32 // 记录 writer 请求锁时需要等待 read 完成的 reader 的数量
- 一个常量:const rwmutexMaxReaders = 1 << 30=1073741824//定义了最大的 reader 数量
易错场景
- 不可复制
- 重入导致死锁
- writer 重入调用 Lock 的时候,就会出现死锁的现象
- 在 reader 的读操作时调用 writer 的写操作
- writer 依赖活跃的 reader -> 活跃的 reader 依赖新来的 reader -> 新来的 reader依赖 writer。
- 释放未加锁的 RWMutex
WaitGroup
用于等待一组goroutine完成执行的同步原语
方法
- Add:用来设置 WaitGroup 的计数值
- Done:用来将 WaitGroup 的计数值减 1,其实就是调用了 Add(-1)
- Wait:调用这个方法的 goroutine 会一直阻塞,直到 WaitGroup 的计数值变为 0
结构体
- noCopy
- 辅助 vet 工具检查是否通过 copy 赋值这个 WaitGroup实例。不能在第一次使用之后复制使用
- state1
- waiter数
- 计数值
- 信号量
常见错误
- 计数器设置为负值
- 调用 Add 的时候传递一个负数
- 调用 Done 方法的次数过多,超过了 WaitGroup 的计数值
- 不期望的 Add 时机。 并发Wait 和 Add,会出现 panic。
Once
用来执行且仅仅执行一次动作,常常用于单例对象的初始化场景
方法
- Do:只有第一次调用会执行
结构体
- done uint32 标记是否已经执行过Do方法
- m 保证只有一个 goroutine 执行Do方法
错误
- 死锁,Do方法里面再次调用Do方法
- 未初始化,Do方法执行失败不会再次执行
Cond
等待某个条件的一组 goroutine,等条件变为 true 的时候,其中一个 goroutine或者所有的 goroutine 都会被唤醒执行(等待-条件-通知)
方法
- Broadcast:唤醒等待队列中的所有waiter
- Signal:唤醒等待队列中的第一个waiter
- wait:
- 把调用者放到等待队列中并阻塞,直到被Broadcast或者Signal唤醒并从等待队列中删除
- 必须要持有锁(一般Mutex 或者 RWMutex)
结构体
- L 绑定的锁
- notifyList 等待 / 通知的队列
- copyChecker 辅助结构,检查 Cond 是否被复制使用
常见错误
- 调用 Wait 的时候没有加锁
- 只调用了一次 Wait,没有检查等待条件是否满足,结果条件没满足,程序就继续执行了( waiter 被唤醒不等于等待条件被满足)
Map
并发安全、键值对、无序
key类型
- K 必须是可比较的
- 不可比较类型:slice、map、函数、struct包含slice也不可以当key
方法
- Store
- Load
- Delete
- Range
优点
- 空间换时间
- 优先从read字段增删查改,读取不需要加锁
- 动态调整
- double-checking
- 延迟删除
Pool
来缓存一组可独立访问的临时对象,避免反复创建销毁带来的性能损耗
特点
- 并发安全
- 不可以在使用之后复制
方法
- New:创建新的元素
- Get:取走元素
- Put:返回元素
结构体
- noCopy
- local:用来存储当前主要的空闲可用的元素
- localSize
- victim:用来存储空闲的元素。垃圾回收时,把 victim 中的对象移除,然后把 local 的数据给 victim,local 就会被清空
- victimSize
- New
坑
- 内存泄露
- 内存浪费