技术分享之《Golang高级讲义》

前言:

      上次在公司分享了golang在高性能场景下的实战调优,看大家洋溢外露的表情来说(请大家吃完饭后),应该还受欢迎的。公司现在好多同学也都慢慢的转向了golang语言,上面的意思也是倾向于转golang。我司php同学全都在转,所以大家也要上进了。

      题外话,貌似社区里有些人总喷人家php低效,一个server可能会开几百个php-fpm,  但别人的选择肯定是有他们的各种原因,外来人没必要对扯吧。现在厉害了,人家直接改用golang了。说实话,我现在工位对面是一整排的php们,现总听他们说高并发,我内心还是有些怕的。 

      我曾经做过外面做过两次python语言的分享,一次是python gil解密,另一次是 python 内存管理。 我个人很喜欢研究探索语言本身的一些底层实现,像golang也一样。 golang前后搞了有几年,但也都是断断续续的折腾,并不是所有公司都喜好golang。 记得2014年那会看过雨痕大神的golang学习笔记,很受感触,算是我对golang底层的第一次接触。随着golang知识的不断积累,慢慢也写了不少的文章。 但到现在为止,写过的golang文章基本是关于问题解决和性能调优方面的,没有runtime相关的描述。 

      该文章后续仍在不断的更新修改中, 请移步到原文地址 http://xiaorui.cc/?p=5208

      所以,干脆做一个关于golang runtime的一些分享得了。 分享的具体topic包含了,go调度器,内存分配器,垃圾回收gc,逃逸分析,管道channel,sync lock/ pool/ mutex的原理等。 因为涉及面太广,我自己也对一些技术点理解不够,大家也都在相互探索中。 我把公司里搞java大数据的,nginx c 开发的同事都拉过来了,ppt是一直被打断,大家也在不断的拉远话题,针对某个点讨论推敲各个语言的优劣。本来预计一个半小时的分享,最后时间直接拉长到三个小时。

 

 

该文章后续仍在不断更新中, 请移步到原文地址  http://xiaorui.cc/?p=5138

ppt的分享地址

ppt在github中的地址,https://github.com/rfyiamcool/share_ppt  , 记得给我star和follow啊.

slideshare的分享地址,https://www.slideshare.net/rfyiamcool/golang-advance-96566124

ppt的下载地址, http://xiaorui.cc/static/golang_advance.pdf

提取ppt中的文本内容

http://xiaorui.cc

1. Golang	⾼高级讲义 xiaorui.cc	github.com/rfyiamcool v 0.2
2. menu slice、map runtime.scheduler memory gc channel escape analysis netpoll timer sync lock sync map sync pool
3. slice 10 20 30 40 50 ptr len=2 cap=4 ptr len=3 cap=3 s2 := arr[2:] s1 := arr[1:3] arr := [5]int{10, 20, 30, 40, 50} • ptr • 底层数组 • len • 占⽤用个数 • cap • 容量量⼤大⼩小
4. slice • 函数值传递只需24字节 (array地址 + len + cap) • append • append会改变array的数据 • 当cap不不⾜足时,会重新make⼀一个新slice替换 ptr • 函数传slice指针可解决ptr变更更问题 • slice 线程不不安全
5. map
6. map hash table hash1 hash2 … hash x bucket key1 … key8 value1 … value8 bucket key1 … value8 overflow bucket key1 … key8 value1 bucket key1 … value8
7. map • map 可理理解为引⽤用 • map 内存释放问题 • go 1.10 value指针和值的性能 • map 线程不不安全 • sync.map • segment lock map
8. scheduler
9. syscall User Application system call interface SYSTEM C Library function User
 Mode Kernel Mode open() write()
10. syscall • how trap syscall • when syscall ?
11. what PMG G1 P1 goroutine m1 thread runQ
12. schedulerG1 m0 P1 G G G G2 m1 P2 G G G p link m only at the same time p count affect parallel more P
13. ext runtime struct 全局M列列表 runtime.allm 存放所有M的列列表 全局P列列表 runtime.allp 存放所有P的列列表 全局G列列表 runtime.allg 存放所有G的列列表 调度器器空闲M列列表 runtime.sched.midle 存放空闲M的列列表 调度器器空闲P列列表 runtime.sched.pidle 存放空闲P的列列表 调度器器可运⾏行行G队列列 runtime.shced.runq 存放可运⾏行行的G队列列 P可运⾏行行的G队列列 runq 存放当前P中可运⾏行行G … … …
14. when to scheduler channel garbage collection blocking syscall like file or network IO time.Sleep more
15. P status • Pgcstop • 正在进⾏行行垃圾回收 • Pidle • 没有跟M关联 • Prunning • 跟M关联 • Psyscall • 系统调⽤用 • Pdead • 减少 GOMAXPROCS
16. M status • ⾃自旋中(spinning) • M正在从运⾏行行队列列获取G, 这时候M会拥有⼀一个P • 执⾏行行go代码中 • M正在执⾏行行go代码, 这时候M会拥有⼀一个P
 • 执⾏行行原⽣生代码中 • M正在执⾏行行原⽣生代码或者阻塞的syscall, 这时M并不不拥有P
 • 休眠中 • M发现⽆无待运⾏行行的G时会进⼊入休眠, 并添加到空闲M链表中, 这时M并不不拥有P

17. Goroutine status
18. goroutine视⻆角 go, 产⽣生新的g, 放到本地队列列或全局队列列 gopark, g置为waiting状态, 等待显示goready唤醒, 在poller中⽤用得较多 goready, g置为runnable状态, 放⼊入全局队列列 gosched, g显示调⽤用runtime.Gosched让出资源, 置为runnable状态, 放 ⼊入全局队列列 goexit, g执⾏行行完退出, g所属m切换到g0栈, 重新进⼊入schedule g陷⼊入syscall: net io和部分file io, 没有事件则gopark; 普通的阻塞系统调⽤用返回时,m重新进⼊入schedule
19. work stealing G G G G G G G G G G G P P P M M M G go func() G find G
20. go work stealing 1. pop from local runq 2. pop from global runq 3. pop from netpoller 4. pop from other p’ runq
21. 抢占 G G G G P P sysmon if block ? M M G M syscall block • prunning • pmg long time • psyscall • syscall long time
22. sysmon 1. netpoll(获取fd事件) 2. retake(抢占) 3. forcegc(按时间强制执⾏gc) 4. scavenge heap(释放⾃由列表中多余的项 减少内存占⽤) 5. more 循环调⽤用存在于整个⽣生命周期 min = 20us; max = 10ms 多功能 sysmon !
23. unlink m & g case G1 m0 P G G G1 Epoll Event G network io 1. network io syscall 2. unlink m & g 3. m pop from runq 4. m run g context
24. unlink p&m case G1 m0 P G G G1 m1 syscall G disk io 1. disk io syscall 2. ulink p & m 3. wakep or newM 4. link p & m
25. memory
26. memory • 基于tcmalloc构建的多级内存池 • mcache: per-P cache,可以认为是 local cache。 • mcentral: 全局 cache,mcache 不不够⽤用的时候向 mcentral 申请。 • mheap: 当 mcentral 也不不够⽤用的时候,通过 mheap 向操作系统申请。 • mspan: 内存分配的基本单位
27. memory • mspan 默认 8k 起 • spans 512M • bitmap 16G • arena 512G
28. memory free[128] spans bitmap … arena_start arena_used arena_end central[67] Mheap mcentral 全局67个 lock sizeclass … tiny tinyoffset local_nsmallfree local_nlargefree alloc[67] mcache; 跟P数⽬目相等 next prev startAddr sizeclass freelist spans bitmap arena mspan 系统虚拟地址空间
29. memory // class bytes/obj bytes/span … // 2 16 8192 // 3 32 8192 // 4 48 8192 // 5 64 8192 … // 23 448 8192 … // 43 4096 8192 … // 64 27264 81920 // 65 28672 57344 // 66 32768 32768 tiny tinyoffset alloc[67] next prev sizeclass startAddr freelist next prev sizeclass startAddr freelist alloc = [67]sizeclass 每⾏行行的sizeclass包含 ⼀一个mspan链表 mcache mspan … 4k 4k if malloc 30 byte ;
30. alloc memory rule • 如果object size>32KB, 则直接使⽤用mheap来分配空间; • 如果object size<16Byte, 则通过mcache的tiny分配器器来分配; • 如果16Byte < object size < 32KB,⾸首先尝试通过sizeclass对应的分配器器分 配; • 如果mcache没有空闲的span, 则向mcentral申请空闲块; • 如果mcentral也没空闲块,则向mheap申请并进⾏行行切分; • 如果mheap也没合适的span,则向系统申请新的内存空间。
31. object relase rule • 如果object size>32KB, 直接将span返还给mheap的⾃自由链; • 如果object size<32KB, 查找object对应sizeclass, 归还到mcache⾃自由 链; • 如果mcache⾃自由链过⻓长或内存过⼤大,将部分span归还到mcentral; • 如果某个范围的mspan都已经归还到mcentral,则将这部分mspan归还 到mheap⻚页堆; • ⽽而mheap会定时将释放的内存归还到系统;
32. gc
33. desc gc Golang 采⽤用了了标记清除的GC算法 Golang的标记清除是⼀一个三⾊色标记法的实现,对于三⾊色标记法,"三⾊色"的概 念可以简单的理理解为: ⽩白⾊色:还没有搜索过的对象(⽩白⾊色对象会被当成垃圾对象) 灰⾊色:正在搜索的对象 ⿊黑⾊色:搜索完成的对象(不不会当成垃圾对象,不不会被GC)
34. when trigger gc • gcTriggerHeap • 当前分配的内存达到⼀一定值就触发GC • default GCGO=100 • gcTriggerTime • 当⼀一定时间没有执⾏行行过GC就触发GC • two minutes • gcTriggerCycle • 要求启动新⼀一轮的GC, 已启动则跳过, ⼿手动触发GC的runtime.GC()会使⽤用这个条件 • gcTriggerAlways • 强制触发GC
35. gc process 1. ⾸首先创建三个集合:⽩白、灰、⿊黑。 2. 将所有对象放⼊入⽩白⾊色集合中。 3. 然后从根节点开始遍历所有对象,把可追溯的对象从⽩白⾊色集合放⼊入灰⾊色集合。 4. 之后遍历灰⾊色集合,将灰⾊色对象引⽤用的对象从⽩白⾊色集合放⼊入灰⾊色集合,之后将此灰 ⾊色对象放⼊入⿊黑⾊色集合。 5. 重复4直到灰⾊色中⽆无任何对象。 6. 通过写屏障检测对象有变化,重复以上操作。 7. 回收所有⽩白⾊色对象
36. GC 扫描标记
37. GC 清除
38. GC 重置
39. forcegc & return os Sysmon 会强制两分钟进⾏行行⼀一次 GC 每5分钟会释放⼀一些span, 归还操作系统
40. map的释放情况 ⼤大量量的使⽤用big map下会出现对象被清楚,但释放不不⼲干净 ! map = nil debug.FreeOSMemory()
41. channel的释放情况 Channel被清空后,⼤大约10分钟释放内存 .
42. debug.FreeOSMemory() 元素被gc清除后,什什么时候释放内存归还 OS ? 编码说 5分钟, 但事实不不是这样… ⼿手动触发 FreeOSMemory() 会更更好的释放 !
43. debug GODEBUG=gctrace=1 func printMem() { runtime.ReadMemStats(&mem) log.Println("mem.Sys: ", mem.Sys/1024/1024) log.Println("mem.Alloc: ", mem.Alloc/1024/1024) log.Println("mem.TotalAlloc: ", mem.TotalAlloc/1024/ log.Println("mem.HeapAlloc: ", mem.HeapAlloc/1024 log.Println("mem.HeapSys: ", mem.HeapSys/1024/1 log.Println("mem.HeapObjects: ", mem.HeapObjects }
44. keyword 写屏障 ? 并发GC ? heap 内存什什么时候归还系统? golang1.9 gc的改进 ?
45. stack
46. stack
47. 逃逸分析 Stack Heap
48. 逃逸分析 产⽣生引⽤用, 直接堆逃逸
49. 逃逸分析 fmt引起的对象堆逃逸...
50. 逃逸分析 所以; 不不要肯定的认为 某个 对象在heap or stack上 . • 分析汇编 • go tool compile -S xxx.go • 对象分布分析 • go tool compile -m xxx.go
51. netpoller func (fd *FD) Read() --> func (pd *pollDesc) wait --> func poll_runtime_pollWait --> func netpollblock() 阻塞中 …
52. netpoller sysmon 唤醒中 …
53. go timer timer.After timer.NewTimer timer.Tick
54. go timer 小顶堆
55. sync.pool obj obj obj objP P obj obj obj obj GC
56. sync.pool • 使⽤用slice做缓冲对象存储 • mutex 保护 shared slice • shared会被偷⾛走, private 留留个本
57. sync.map
58. sync.map
59. sync.Mutex G G G G G1 G2 sema waiting queue • cas • 适当减少syscall • 通过runtime调度唤醒
60. sync.Mutex G G G G G1 G2 sema waiting queue • atomic.CompareAndSwap • 适当减少syscall • 通过runtime调度唤醒
61. • 双检查锁机制 • 使⽤用互斥量量保护wait queue
62. sync.Mutex Mutex也不不廉价
63. sync.Mutex • sync.Mutex 锁性能不不是问题 • noDeferLock 轻易易⼲干到 5000w/s • DeferLock 也可以到 1700w/s • 锁竞争才是最⼤大问题 open 10个Goroutine
64. 思考 ? • 为什什么已经有cas指令,还在syscall futex ? • golang 底层都谁在调⽤用futex锁 ? • 是否可以适当规避futex的使⽤用 ?
65. channel设计 buf sendx recvx lock sendq recvq closed circular queue send index receive index mutex Hchan 2 1 0 ch := make(chan Task, 3) 1 0 Heap alloc an hchan on the heap wait send q wait recv q g elem … g1 task sudog
66. send full channel? G1 calls into the scheduler set G1 to waiting unlink G1, M ch <- task, but chan is full G1 M P G2 runnable G1 waiting schedule a runnable G from the P runqueue runQ is block, so gopark()
67. resuming goroutines G2 calls into the scheduler set G1 to runnable puts it on runqueue task := <- chan, then sender can run G2 M P G1 runnable return to G2 runQ goready(G1) G0 current G
68. direct write G4 Stack G3 Stack stack Heap G3直接把数据copy给G4 sudog⾥里里elem地址!
69. tools go tool pprof go tool trace go-torch go test -bench=. -benchmem …
70. link https://github.com/qyuhen https://www.youtube.com/watch?v=C1EtfDnsdDs https://github.com/golang/go/tree/master/src https://github.com/golang/go/issues
71. “Q & A” –rfyiamcool

贴上一些PPT的截图.

更多截图,请到上面地址瞅瞅。


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc