文章目录
参考资料
https://github.com/talkgo/night
https://github.com/talkgo/read
defer
- 当defer被声明时,其参数就会被实时解析,传进去的i,就是当时i的值
- defer执行顺序为先进后出
- defer 可以修改有名返回值函数的返回值
并发
channel
源码解析
- buf是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表
- sendx和recvx用于记录buf这个循环链表中的发送或者接收的index
- lock是个互斥锁。
- recvq和sendq分别是接收(<-channel)或者发送(channel <- xxx)的goroutine抽象出来的结构体(sudog)的队列。是个双向链表
- 在发送方关闭channel
- 关闭已经关闭的channel会异常
select
- 轮询顺序 pollOrder 和加锁顺序 lockOrder 分别是通过以下的方式确认的:
- 轮询顺序:通过 runtime.fastrandn 函数引入随机性;
- 加锁顺序:按照 Channel 的地址排序后确定加锁顺序,避免死锁;
- selectgo 构建出来的 sudog 会将 isSelect 置为 true,这样时为了避免多个 channel 从等待队列中获取相同 goroutine 封装的 sudog
锁
Mutex
- lock():
- 通过CAS获取锁
- 通过自旋获取锁
逃逸分析
- 指针逃逸
- 切片逃逸
- 对象过大
context
用来在 goroutine 之间传递上下文信息,控制协程的生命周期
协程
概念
- G:一个协程,里面装有上下文信息等
- P:一个队列,装有协程
- M:一个操作器,将协程搬运到线程上执行
- g0 是为每个 OS 线程创建的第一个 goroutine,g0 会把就绪状态的 Goroutine 调度到线程上去运行。g0作用及解析
GPM模型
https://learnku.com/articles/41728
- G1创建了G2
- G1阻塞系统调用:P1将与M1解绑,寻找新的M
- G1非阻塞系统调用:P1将与M1解绑,但M1会记住P1,M1空闲了会找P1拿G
调度时机
- 主动挂起 —runtime.gopark->runtime.park_m
- 系统调用 —runtime.exitsyscall->runtime.exitsyscall0
- 协作式调度 —runtime.Gosched->runtime.gosched_m->runtime.goschedImpl
- 系统监控 — runtime.sysmon -> runtime.retake -> runtime.preemptone
内存管理
这篇文章足够了
GC
触发时机
-
2min强制一次
-
步调算法:控制内存增长的比例
-
runtime.GC
过程
行为 | 会不会 STW | 为什么 |
---|---|---|
标记开始 | 会 | 打开写屏障,准备根对象的扫描 |
并发标记中 | 不会 | 1. 初始25%CPU用来标记 2.堆内存快增长到Goal,会抢占用户P用来并发标记(辅助标记) |
标记结束 | 会 | 重新扫描根对象 |
GC日志
设置变量GODEBUG gctrace =1
gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P
- gc#:GC 执行次数的编号,每次叠加。
- @#s:自程序启动后到当前的具体秒数。
- #%:自程序启动以来在GC中花费的时间百分比。
- #+…+#:GC 的标记工作共使用的 CPU 时间占总 CPU 时间的百分比。
- #->#-># MB:标记启动前堆内存大小,标记结束后堆内存大小,标记为存活的对象大小
- #MB goal:辅助标记的触发点
- #P:当前使用的处理器 P 的数量。
基础知识
make和new的区别
- new:函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针
- make:只用于 chan、map 以及 slice 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
类型转换及判断
- reflect.TypeOf(v1) //获取类型信息:具体文章
- vs, ok := v1.(string)//v1转为string类型,ok判断转换是否成功