https://github.com/Junedayday/code_reading/blob/master/basic/goroutine.md
Goroutine相关
WaitGroup
- 理解WaitGroup的实现 - 核心是
CAS
的使用 - Add与Done应该放在哪? -
Add放在Goroutine外,Done放在Goroutine中
,逻辑复杂时建议用defer保证调用 - WaitGroup适合什么样的场景? -
并发的Goroutine执行的逻辑相同时
,否则代码并不简洁,可以采用其它方式
分析了源码,我们可以总结如下:
- Add方法与wait方法不可以并发同时调用,Add方法要在wait方法之前调用.
- Add()设置的值必须与实际等待的goroutine个数一致,否则会panic.
- 调用了wait方法后,必须要在wait方法返回以后才能再次重新使用waitGroup,也就是Wait没有返回之前不要在调用Add方法,否则会发生Panic.
- Done 只是对Add 方法的简单封装,我们可以向 Add方法传入任意负数(需要保证计数器非负)快速将计数器归零以唤醒等待的 Goroutine.
- waitGroup对象只能有一份,不可以拷贝给其他变量,否则会造成意想不到的Bug.
Context
- Context上下文 - 结合Linux操作系统的
CPU上下文切换/子进程与父进程
进行理解 - 如何优雅地使用context - 与
select
配合使用,管理协程的生命周期 - Context的底层实现是什么? -
mutex
与channel
的结合,前者用于初始部分参数,后者用于通信
Channel
- channel用于Goroutine间通信时的注意点 - 合理设置channel的size大小 / 正确地
关闭channel
- 合理地运用channel的发送与接收 - 运用函数传入参数的定义,限制
<- chan
和chan <-
- channel的底层实现 -
环形队列
+发送、接收的waiter通知
,结合Goroutine的调度思考 - 理解并运用channel的阻塞逻辑 - 理解channel的每一对
收与发
之间的逻辑,巧妙地使用 - 思考channel嵌套后的实现逻辑 - 理解用
chan chan
是怎么实现两层通知
的?
sync.Map
- sync.Map的核心实现 - 两个map,一个用于写,另一个用于读,这样的设计思想可以类比
缓存与数据库
- sync.Map的局限性 - 如果写远高于读,dirty->readOnly 这个类似于
刷数据
的频率就比较高,不如直接用mutex + map
的组合 - sync.Map的设计思想 - 保证高频读的无锁结构、空间换时间
sync.Cond
- sync.Cond的核心实现 - 通过一个锁,封装了
notify 通知
的实现,包括了单个通知
与广播
这两种方式 - sync.Cond与channel的异同 - channel应用于
一收一发
的场景,sync.Cond应用于多收一发
的场景 - sync.Cond的使用探索 - 多从专业社区收集意见 https://github.com/golang/go/issues/21165
sync.Pool
- sync.Pool的核心作用 - 读源码,
缓存稍后会频繁使用的对象
+减轻GC压力
- sync.Pool的Put与Get - Put的顺序为
local private-> local shared
,Get的顺序为local private -> local shared -> remote shared
- 思考sync.Pool应用的核心场景 -
高频使用且生命周期短的对象,且初始化始终一致
,如fmt - 探索Go1.13引入
victim
的作用 - 了解victim cache
的机制 - 为了使得在多个goroutine中高效的使用goroutine,sync.Pool为每个P(对应CPU)都分配一个本地池,当执行Get或者Put操作的时候,会先将goroutine和某个P的子池关联,再对该子池进行操作。 每个P的子池分为私有对象和共享列表对象,私有对象只能被特定的P访问,共享列表对象可以被任何P访问。因为同一时刻一个P只能执行一个goroutine,所以无需加锁,但是对共享列表对象进行操作时,因为可能有多个goroutine同时操作,所以需要加锁。
atomic
atomic
适用的场景 - 简单、简单、简单!不要将atomic用在复杂的业务逻辑中atomic.Value
与mutex
- 学习用两者解决问题的思路- 了解
data race
机制 - atomic可以有效地减少数据竞争