并发内置数据结构
sync.Once
sync.Once 只有⼀个⽅法,Do()
但 o.Do 需要保证:
- 初始化⽅法必须且只能被调⽤⼀次
- Do 返回后,初始化⼀定已经执⾏完成
sync.Pool
主要在两种场景使⽤:
- 进程中的 inuse_objects 数过多,gc mark 消耗⼤量 CPU
- 进程中的 inuse_objects 数过多,进程 RSS 占⽤过⾼
请求⽣命周期开始时,pool.Get,请求结束时,pool.Put。在 fasthttp 中有⼤量应⽤
https://github.com/valyala/fasthttp/blob/b433ecfcbda586cd6afb80f41ae45082959dfa91/server.go#L402
sync.Pool 发⽣ GC 时:
semaphore
是锁的实现基础,所有同步原语的基础设施。
sync.Mutex
sync.RWMutex
sync.Map
https://www.figma.com/proto/FMzUIdkjm4BEHSpwWFecew/concurrency?page-id=6%3A15&node-
id=6%3A16&viewport=-46%2C368%2C0.5078045725822449&scaling=min-zoom
sync.Waitgroup
Counter 减到 0 时,要唤醒所有 sema 上阻塞的 sudog
并发编程模式举例
CSP 和传统并发模式
Fan-in,合并多个 channel 操作
Or channel
任意 channel 返回
全部返回
Pipeline
串联在⼀起的 channel
并发同时保序
常⻅的并发 bug
死锁
RWR 死锁
循环等待死锁
注意:死锁问题需要通过 pprof 进⼊ goroutine ⻚⾯查看
Map concurrent writes/reads
崩溃时输出 stderr,请注意重定向你的 stderr 到单独的⽂件中
Channel 关闭 panic
Channel closing principle
- M receivers, one sender, the sender says “no more sends” by closing the data channel
- One receiver, N senders, the only receiver says “please stop sending more” by closing an additional signal channel
- M receivers, N senders, any one of them says “let’s end the game” by notifying a moderator to close an additional signal channel
fn() 超时后,ch <- result 阻塞
goroutine
永久泄露
wait group 使⽤不当,永久阻塞
context.WithCancel
内部启动 goroutine,在 ctx 被覆盖后泄露
死锁
闭包捕获本地变量
启动goroutine前要保证Add完成
并发操作 channel 时,多次关闭同⼀个 channel
Fn 耗时很久,但进⼊之前没有判断外部给的stopCh 中的通知浪费算⼒
内存模型
对于应⽤开发的同学来说,只要记住,使⽤显式同步就可以保证正确性。
现代计算机的多级存储结构
L1D cache ⼜会被划分为多个cache line,每个 cache line = 64 bytes
http://15418.courses.cs.cmu.edu/spring2015/lecture/basicarch/slide_042
L1 cache ⼜被划分为更细粒度的 cacheline,下⾯是在服务器上获取 L1 cache line size 的命令
1 | $ getconf LEVEL1_DCACHE_LINESIZE |
Runtime 中的 cacheline pad
http://15418.courses.cs.cmu.edu/spring2015/lecture/basicarch/slide_042
多核⼼给我们带来的问题:
- 单变量的并发操作也必须⽤同步⼿段,⽐如 atomic
- 全局视⻆下观察到的多变量读写的顺序可能会乱序
单变量的原⼦读/写,多核⼼使⽤ mesi 协议保证正确性
Mesi 协议是以整个 cache line 为单位进⾏的
https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm
多核⼼执⾏时,CPU 和编译器可能对读写指令进⾏重排,使⽤ Litmus 测试观察内存重排:
检查两个核⼼的 EAX 是不是都为 0
https://github.com/herd/herdtools7
False sharing:因为 CPU 处理读写是以 cache line 为单位,所以在并发修改变量时,会⼀次性将其它 CPU core 中的cache line invalidate 掉,导致未修改的内存上相邻的变量也需要同步,带来额外的性能负担。
True sharing:多线程确实在共享并更新同⼀个变量/内存区域。
Happen-before 到底是什么?
同⼀个 goroutine 内的逻辑有依赖的语句执⾏,满⾜顺序关系。
编译器/CPU 可能对同⼀个 goroutine 中的语句执⾏进⾏打乱,以提⾼性能,但不能破坏其应⽤原有的逻辑。
不同的 goroutine 观察到的共享变量的修改顺序可能不⼀样。
初始化:
A pkg import B pkg,那么 B pkg 的 init 函数⼀定在 A pkg 的 init 函数之前执⾏。
Init 函数⼀定在 main.main 之前执⾏
Goroutine 创建:
Goroutine 的创建(creation)⼀定先于 goroutine 的执⾏(execution)
Goroutine 结束:
在没有显式同步的情况下,goroutine 的结束没有任何保证,可能被执⾏,也可能不被执⾏
Channel 收/发:
A send on a channel happens before the corresponding receive from that channel completes.
这⾥ c <- 0 ⼀定先于 <- c 执⾏完,所以 print ⼀定能打印出 hello world
The closing of a channel happens before a receive that returns a zero value because the channel is closed.
close(c) ⼀定先于 <-c 执⾏完,所以这⾥也可以保证打印出 hello world。
Channel 收/发:
A receive from an unbuffered channel happens before the send on that channel completes.
⽆ buffer 的 chan receive 先于 send 执⾏完,这⾥也可以保证打印出 hello world
Lock:For any sync.Mutex or sync.RWMutex variable l and n < m, call n of l.Unlock() happens before call m of l.Lock()returns
Unlock ⼀定先于 Lock 函数返回前执⾏完
Once:A single call of f() from once.Do(f) happens (returns) before any call of once.Do(f) returns.
本质是在⽤户不知道 memory barrier 概念和具体实现的前提下,能够按照官⽅提供的 happen-before 正确进⾏并发编程。
Memory barrier
在并发编程中的 memory barrier 和 GC 中的 barrier 不是⼀回事。
Memory barrier 是为了防⽌各种类型的读写重排:
⽽ GC 中的 read/write barrier 则是指堆上指针修改之前插⼊的⼀⼩段代码。
References
https://wudaijun.com/2018/02/go-sync-map-implement/
https://github.com/kat-co/concurrency-in-go-src
https://speakerdeck.com/kavya719/understanding-channels
https://www.zenlife.tk/concurrency-with-keep-order.md?hmsr=joyk.com&utm_source=joyk.com&utm_medium=referral
https://golang.org/ref/mem
https://www.hardwaretimes.com/difference-between-l1-l2-and-l3-cache-what-is-cpu-cache/
https://github.com/lotusirous/go-concurrency-patterns
https://songlh.github.io/paper/go-study.pdf
https://github.com/cch123/golang-notes/blob/master/memory_barrier.md
未涉及
- 内置并发结构:sync.Cond
- 进阶话题:如 acquire、release、sequential consistency。
- Lock-Free,Wait-free 等等
- 扩展并发原语:SingleFlight,ErrGroup 等