Go并发编程研讨课

  • 1303 浏览

鸟窝

2020/10/07 发布于 编程 分类

文字内容
1. Go 并发编程 晁岳攀 @colobu https://colobu.com
2. 目录 D I R E C T O RY 01 02 03 04 同步原语 分布式同步原语 Channel 内存模型 基本同步原语 、 语 语展 语 同步原语、原子操作 语、语语语、 语语列 Leader 语语、 STM 异常情况 三种应用场景 应用模式 内存模型 happen before
3. 0 0 小故事 •Therac-25 事件
4. Go 并发 BUG 研究 ● ● 研究了 Docker 、 Kubernetes 、 etcd 、 gRPC 、 CockroachDB 、 BoltDB 的 bug 两种分类方法 ○ share memory bugs 和 message passing bugs ○ blocking bugs 和 non-blocking bugs
5. Go 并发 BUG 研究 基本同步原语的使用占比
6. Go 并发 BUG 研究 两种类型的同步原语占比
7. Go 并发 BUG 研究 bug 存在的时间
8. Go 并发 BUG 研究 bug 的分类数量
9. Go 并发 BUG 研究 导致 blocking bugs 的 原语
10. Go 并发 BUG 研究 blocking bugs 的修复方 式
11. Go 并发 BUG 研究 能检测出的 blocking bugs
12. Go 并发 BUG 研究 导致 non-blocking bugs 原因
13. Go 并发 BUG 研究 non-blocking bugs 的修复方式
14. Go 并发 BUG 研究 non-blocking bugs 补丁中的原语
15. Go 并发 BUG 研究 能检测出的 non-blocking bugs
16. 0 1 同步原语  基本同步原语  扩展的同步原语  原子操作
17. 基本同步原语
18. 基本同步原语 Mutex ● ● ● ● 互斥锁 Mutual exclusion 任何时间只允许一个 goroutine 在临界区域运行 使用时避免死锁 追求公平 ● ● ● ● 零值是未锁状态 Unlock 未加锁的 Mutex 会 panic 加锁的 Mutex 不和这个特定的 goroutine 关联 非重入锁
19. 基本同步原语 Mutex 初版 (2008)
20. 基本同步原语 Mutex 演化 ● ● ● ● 2012 年 , commit dd2074c8 做了一次大的改语语 ,它将 语 语 waiter count( 等待者的 数量 ) 和锁标识分开来 ( 内部实现还是合用使用 state 字段 ) 。新来的 goroutine 占语语语 ,会有更大的机会 语 语 语 语 语 语 语 语语 取语语 。 2015 年 , commit edcad863, Go 1.5 中 mutex 实现为全协作式的,增加了 spin 机制,一旦有竞争,当前 goroutine 就会进入调度器。在临界区执行很短的 情况下可能不是最好的解决方案。 2016 年, commit 0556e262, Go 1.9 中增加了饥饿模式,让锁变得更公平 ,不公平的等待时间限制在 1 毫秒,并且修复了一个大bug, 唤醒的 goroutine 总 是放在等待队列的尾部会导致更加不公平的等待时间。 2019 年, commit 41cb0ae inline 优化,将 slow path 抽取出来,保留 fast path 以便内联优化
21. 基本同步原语 Mutex 当前实现
22. 基本同步原语 Mutex 当前实现的逻辑 ● ● ● ● 互斥语语 有两种状 语 语 语 语语 :正常状 语 语 语 语语 和语语语 状语语 。 在正常状态下,所有等待锁的 goroutine 按照 FIFO 顺序等待。唤醒的goroutine 不会直接拥有锁,而是会和新请求锁的 goroutine 竞争锁的拥有。 如果一个 等待的 goroutine 超过 1ms 没有语语 取语语 ,那么它将会把 语 语 语 语 语 语 语语语语语语语 模式。 语语 在饥饿模式下,锁的所有权将从 unlock 的 gorutine 直接交给交给等待队列中 的第一个。新来的 goroutine 将不会尝试去获得锁,即使锁看起来是 unlock 状语 , 也不会去语语语 自旋操作,而是放在等待 语 语 语 语 语 语 语 语 语 语 语语 列的尾部。 语语语语 如果一个等待的 goroutine 获取 了,并且 锁 满足一以下其中的任何一个条件 : (1) 它是队列中的最后一个; (2) 它等待的时候小于 1ms 。它会将锁的状 态转换为正 态。常状
23. 基本同步原语 Mutex 扩展
24. 基本同步原语 Mutex 前人踩的坑
25. 基本同步原语 RWMutex ● ● ● ● ● 可以被一堆的 reader 持有,或者被一个 writer 持有 适合大并发 read 的场景 零值是未加锁的状态 writer 的 Lock 相对后续的 reader 的 RLock 优先级高 禁止递归读锁
26. 基本同步原语 RWMutex ● ● ● ● ● 可以被一堆的 reader 持有,或者被一个 writer 持有 适合大并发 read 的场景 零值是未加锁的状态 writer 的 Lock 相对后续的 reader 的 RLock 优先级高 禁止递归读锁
27. 基本同步原语 RWMutex 当前实现
28. 基本同步原语 RWMutex 递归的坑
29. 基本同步原语 RWMutex 扩展
30. 基本同步原语 RWMutex 坑 前人踩过得
31. 基本同步原语 Cond ● Mutex 有些情况下不适用 ( 通知机制 ) ● Monitor vs. Mutex , Monitor= Mutex + Condition Variables ● Condition variable 是一组等待同一个条件的 goroutine 的容器 ● 每个 Cond 和一个 Locker 相关联 ● 改变条件或者调用 Wait 需要获取锁
32. 基本同步原语 Cond
33. 基本同步原语 Waitgroup ● ● ● ● 等待一组 goroutine 完成 (Java CountdownLatch/CyclicBarrier) Add 参数可以是负值;如果计数器小于 0, panic 当计数器为 0 的时候,阻塞在 Wait 方法的 goroutine 都会被语语 放 可重用,但是……
34. 基本同步原语 Waitgroup Add 一定要在 Wait 之前设置好 ✔ ✘
35. 基本同步原语 Waitgroup 一定条件下可重用
36. 基本同步原语 Waitgroup 多次 Wait 和多次 Done ✔ ✘
37. 基本同步原语 Waitgroup Wait 和 Add 并发调用
38. 基本同步原语 Waitgroup Wait 未完成时就调用 Add
39. 基本同步原语 Once ● 只执行一次初始化 ● 避免死锁 ● 即使 f panic, Once 也认语语 它完成了 语语语
40. 基本同步原语 Once f panic 也被认为初始化完成
41. 基本同步原语 Once f 内不要再调用此 once
42. 基本同步原语 单例 ● ● ● ● ● package 级别的常量 package 变量 (eager) init 函数 (eager) GetInstance() (lazy) 通过 sync.Once 或者类似实现
43. 基本同步原语 单例的问题 ● io.EOF, http.DefaultClient 认为修改
44. 基本同步原语 单例 错误的实现
45. 基本同步原语 单例 错误的实现 2
46. 基本同步原语 单例 正确的实现
47. 基本同步原语 A XXX must not be copied after first use. ● 零值是无锁的 ● 使用后是有状态的 ● Copy 也会 copy 状态
48. 基本同步原语 A XXX must not be copied after first use. ● go vet 可以检查 ● 通过嵌入 noCopy 帮助 vet 工具检查
49. 基本同步原语 Copy 总在不经意间发生 ● ● ● ● ● ● ● 嵌套的 struct 包含非指针类型的同步原语 变量赋值 函数 / 方法传参 函数 / 方法的返回值 方法的 Receiver range 语句 ......
50. 基本同步原语 Pool ● ● ● ● 临时对象池 可能在任何时候任意的对象都可能被移除 可以安全地并发访问 装箱 / 拆箱
51. 基本同步原语 Pool 容易内存泄漏 go#23199
52. 基本同步原语 Pool fmt 包错误使用 go#27740
53. 基本同步原语 Pool json 包错误使用 go#2773
54. 基本同步原语 Pool Socket 连接池
55. 基本同步原语 Pool 使用链表进行重用
56. 基本同步原语 sync.Map
57. 基本同步原语 sync.Map ● 两个场景 ○ 设置一次,多次读 ( 比如 cache) ○ 多个 goroutine 并发的读、写、更新不同的 key ● 装箱 / 拆箱 ● Range 进行遍历 , 可能会加锁 ● 没有 Len 方法,并且也不会添加
58. 基本同步原语 context.Context ● 传递上下文 ( 本来含义 ) ● 取消 goroutine 的运行 ( 扩展功能。主动取消和超时取消 ) ● 一般用在函数的第一个参数 ● 不要嵌入到 struct 中
59. 基本同步原语 context.Context ctx ctx ctx ctx ctx ctx 链式查找 (WithValue) := context.Background() = context.TODO() = context.WithValue(ctx, = context.WithValue(ctx, = context.WithValue(ctx, = context.WithValue(ctx, "key1", "key2", "key3", "key4", "0001") "0001") "0001") "0004") fmt.Println(ctx.Value("key1")) 查找 key1 key4:00 04 key3:00 03 key2:00 02 key1:00 01
60. 基本同步原语 context.Context 控制程序的运行 都返回子 context 和 cancelFunc cancelFunc 只需调用一次,后续的调用不会做额外的工作 cancelFunc 被调用,或者 Parent 的 Done 被 close ,这个子 context 的 Done 也会被 close, Err 值也会被设置 ● 尽早的调用 cancelFunc 释放资源 ● WithDeadline/withTimeout 也会和 parent 的时间进行比较,使用最 早的 deadline ● ● ● ●
61. 基本同步原语 context.Context pros ● 方便传递上下文 (request-scoped) ● 可以控制子 goroutine 的运行 (channel 的 Done 模式 ) ● 无限级的函数传递
62. 基本同步原语 context.Context cons ● Context isn’t for cancellation ● Context should go away for Go 2 ● 函数污染,想象一下 Reader/Writer 等所有的函数都不得不增加 context 作为第一个参数
63. 基本同步原语 time.Timer ● wall clock 用来显示时间, monotonic clock 用来测量时间 ● Stop 返回 bool 值 ,false 表示事件已经触发或者已经 stopped;Stop 不会 close channel ● Stop 之后要 drain channel ● Stop AfterFunc 创建的 timer, 并不会等待 f 的完成 ● Stop 可以调用多次 ● 从 channel 中 receive 一次,更多次的 receive 会阻塞 ● 小心 time.After 导致的暂时的内存泄漏
64. 基本同步原语 time.Ticker ● d 必语 大于零,否 语 语 语 语 语 语 panic ● channel 的 buffer 大小为 1, 这意味着如果没有读取,后续的事件会 被丢弃 ● 如果不及时读取,导致的实际间隔可能不是 d, 有可能两个事件几乎 同时发生
65. 基本同步原语 RandTicker 一定范围内的随机间隔
66. 扩展同步原语
67. 扩展同步原语 ReentrantLock goid
68. 扩展同步原语 ReentrantLock token
69. 扩展同步原语 Semaphore ● ● ● ● Dijkstra 提出并发访问通用资源的同步原语 初始化一个非负的值 S P(wait) 减一,如果 S 小于 0 ,阻塞本 goroutine 进入临界区 V(signal) 加一,如果 S 不为负值,其它 goroutine 可以进入临界区 ● 二进制信号量可以实 (0,1) 现锁 ● 计数信号量
70. 扩展同步原语 Semaphore golang.org/x/sync/semaphore
71. 扩展同步原语 SingleFlight golang.org/x/sync/singleflight go/src/internal/singleflight/singleflight.go
72. 扩展同步原语 SingleFlight 应用 ● 标准库
73. 扩展同步原语 SingleFlight 应用 ● groupcache
74. 扩展同步原语 ErrGroup golang.org/x/sync/semaphore ● Wait 会等待所有的 goroutine 语 行 完后才 语语放 ● 如果想遇到第一个 err 就返回,使用 Context
75. 扩展同步原语 SpinLock ● ● ● ● 自旋锁 有些场景很高效,但是 非公平 处理器忙等待
76. 扩展同步原语 FileLock github.com/juju/fslock 跨进程的 Mutex
77. 扩展同步原语 concurrent-map github.com/orcaman/concurrent-map
78. 原子操作
79. 原子操作 atomic 数据类 型 ● ● ● ● ● ● int32 int64 uint32 uint64 uintptr unsafe.Pointer
80. 原子操作 atomic 函数 ● ● ● ● ● AddXXX ( 整数类型) CompareAndSwapXXX : cas LoadXXX :读取 StoreXXX :存储 SwapXXX :交换
81. 原子操作 atomic 函数 ● 有 Add 没有 Subtract ? ○ 有符号的类型,可以使用 Add 负数 ○ 无符号的类型,可以使用 AddUint32(&x, ^uint32(c-1)),AddUint64(&x, ^uint64(c1)) ○ 无符号类型减一, AddUint32(&x, ^uint32(0)) , AddUint64(&x, ^uint64(0))
82. 原子操作 atomic 通用对象 ● Value
83. 原子操作 atomic 实现 AMD64 (windows)
84. 原子操作 atomic 实现 386 及其它 /runtime/internal/atomic
85. 原子操作 Lock-free 算法 ● non-blocking 算法 ○ lock-free: 保证系统的吞吐率, ○ wait-free :保证线程的吞吐率 ○ ringbuffer: single reader single writer ○ read-copy-write:'>read-copy-write: single writer(lock-free), n readers (wait-free) ○ read-copy-write:'>read-copy-write: m writer (with lock), n readers (lock-free) ● 实现: atomic read-modify-write, 实现基本的数据结构 ● 例外:不使用 CAS
86. 0 2 分布式同步原语
87. 分布式同步原语 etcd vs zookeepr vs consul ● zookeeper ○ ZooKeeper Recipes and Solutions ○ Apache Curator Recipes ● consul ○ ● etcd 官方不支持 ○ contrib/recipes ○ clientv3/concurrency ● redis ○ redlock
88. 分布式同步原语 Locker github.com/coreos/etcd/clientv3/concurrency
89. 分布式同步原语 Mutex github.com/coreos/etcd/clientv3/concurrency
90. 分布式同步原语 Mutex github.com/coreos/etcd/clientv3/concurrency
91. 分布式同步原语 RWMutex github.com/coreos/etcd/contrib/recipes
92. 分布式同步原语 RWMutex github.com/coreos/etcd/contrib/recipes
93. 分布式同步原语 Barrier github.com/coreos/etcd/contrib/recipes
94. 分布式同步原语 Barrier
95. 分布式同步原语 Barrier
96. 分布式同步原语 Leader Election github.com/coreos/etcd/clientv3/concurrency
97. 分布式同步原语 Leader Election
98. 分布式同步原语 队列 github.com/coreos/etcd/contrib/recipes
99. 分布式同步原语 队列
100. 分布式同步原语 优先级队列
101. 分布式同步原语 STM software transactional memory
102. 分布式同步原语 micro/gosync github.com/micro/go-sync
103. 0 3 Channel
104. Channel 功能 ● 信号 (shutdown/close/finish) ● 数据交流 (queue/stream) ● 锁 (mutex)
105. nil not empt y empt y full not full close d receiv e block value block value value drained read, return zero value send block write value write value block write value panic close panic close d, draine d read, return zero value closed, return zero value for read closed, drained read, return zero value closed, drained read, return zero value panic Channel 特别场景
106. Channel 内部实现 ● 源代码: https://golang.org/src/runtime/chan.go ● GopherCon 2017: Kavya Joshi - Understanding Channels ● Go 语言 Channel 实现原理精要
107. Channel Locker
108. Channel Locker
109. Channel Channel vs Mutex 过度使用 channel 和 goroutine ● Channel ○ 传递数据的 owner ○ 分发任务单元 ○ 交流异步结果 ○ 任务编排 ● Mutex ○ cache ○ 状态 ○ 临界区
110. Channel Or-Done
111. Channel Or-Done goroutine
112. Channel Or-Done 二分法递归
113. Channel Or-Done 反射
114. Channel Fan In goroutine
115. Channel Fan In goroutine
116. Channel Fan In 递归
117. Channel Fan In 反射
118. Channel Fan Out goroutine
119. Channel Fan Out 反射
120. Channel Fan out roundrobin
121. Channel Fan out 反射
122. Channel Pipeline
123. Channel Stream - Skip
124. Channel Stream - Take
125. Channel Stream - Map
126. Channel Channels over channel
127. 0 4 内存模型 •单击此处添加小标题 •单击此处添加小标题
128. 内存模型 内存模型描述了线程 ( 纤程 ) 通过内存的交互,以及对数据的共享使用 历史 Java Memory Model 第一个尝试定义内存模型的编程语言 WG21/N2429 : Concurrency memory model (final revision),C11/C+ +11 Go 内存模型 定义了一个条件:对同一个变量,如何保证在一个 goroutine 对此变量 读的时候,能观察到其它 goroutine 对此变量的写。 修改一个同时被多个 goroutine 并发访问的变量的时候,需要串行化访 问。 Don't be
129. 内存模型 Happens Before 历史 happens-before 关系是指两个事件结果之间的关系。如果一个事件 happens before 另外一个事件,那么结果应该反映这一点,即使事件是 无序执行的。 • a • a • • • b : 同一个进程中事件 a 在事件 b 之前发生 b : 事件 a 发送消息,事件 b 接受这个消息 transitive( 传递性 ): irreflexive ( 反自反性 ): antisymmetric( 非对称性 ):
130. 内存模型 Happens Before 单个 goroutine 内 读写执行的顺序和程序定义顺序一致 乱序执行不影响程序的行为
131. 内存模型 Happens Before
132. 内存模型 init 函数 init 的执行是在单个 goroutine 中执行的 1. 如果 package p 引入了 package q, 那么 q 的 init 函数一定 happens before p 的 init 之前。 2. main 函数在所有引入的 init 函数执行
133. 内存模型 goroutine ● goroutine 的创建 happens before 所有此 goroutine 中的操作 ● goroutine 的销毁 happens after 所有此 goroutine 中的操作
134. 内存模型 Channel ● 第 n 个 send 一定 happen before 第 n 个 receive 完成 , 不管是 buffered channel 语是 unbuffered channel ● 对于 capacity 为 m 的 channel, 第 n 个 receive 一定 happen before 第 (n+m) send 完成 ● m=0 unbuffered 。第 n 个 receive 一定 happen before 第 n 个 send 完成 ● channel 的 close 一定 happen before receive 端得到通知,得到通 知意味着 receive 收到一个因为 channel close 而收到的零值 注意 send/send completes , receive/receive completes 的区别
135. 内存模型 Channel unbuffered
136. 内存模型 Channel buffered
137. 内存模型 Mutex/RWMutex ● 对于 Mutex/RWMutx m, 第 n 个成功的 m.Unlock 一定 happen before 第 n+1 m.Lock 方法调用的返回 ● 对于 RWMutex rw ,如果它的第 n 个 rw.Lock 已返回,那么它的第 n 个成功的 rw.Unlock 的方法调用一定 happen before 任何一个 rw.RLock 方法调用的返回(它们 happen after 第 n 个 rw.Lock 方 法调用返回) ● 对于 RWMutex rw, 如果它的第 n 个 rw.RLock 已返回,接着第 m (m < n) 个 rm.RUnlock 方法调用一定 happen before 任意的 rw.Lock( 它们 happen after 第 n 个 rw.RLock 方法调用返回之后 )
138. 内存模型 Waitgroup ● 对于 Waitgroup b, 对于其计数器不是 0 的时候,假如此时刻之后 有一组 wg.Add(n), 并且我们确信只有最后一组方法调用使其计数器 最后复原为 0 ,那么这组 wg.Add 方法调用一定 happen before 这 一时刻之后发生的 wg.Wait ● wg.Done() 也是 wg.Add(-1)
139. 内存模型 Once ● once.Do 方法的执行一定 happen before 任何一个 once.Do 方法 的返回
140. 内存模型 Atomic ● 没有官方的保证 ● 建议是不要依赖 atomic 保证内存的顺序 ● #5045 历史悠久的讨论,还没 close
142. The End