go的调度相关
- v1.1 非抢占式调度
- v1.2 基于协助的抢占式调度
- v1.14 基于信号的抢占式调度
- 协程运行超过20ms就会被强行夺取cpu运行权。
-
golang 反射 juejin.cn/post/713496…
-
golang map juejin.cn/post/713338…
golang 内存分配
-
栈内存 由编译器自动分配和释放
- 初始值为2kb,函数调用之前会检查栈空间大小,扩容会扩大到原有的两倍,最大为1gb
- 缩容:当利用率小于1/4时进行缩容,缩为原有的1/2.
-
堆内存
-
变量的内存分配
- 局部变量由编译器控制,如果函数内的局部变量在外部仍需使用,则分配到堆上,没有使用则分配在栈上。
struct是否可以比较
- 不可以比较的类型:slice,map和function
- 其他的都可以比较
- 对于指针:两个空指针是相同的,指向同一个对象的指针也是相同的
- reflect.DeepEqual可以对两个对象进行比较
defer
- 在函数退出的时候执行,或者发生panic的时候执行,先加入defer的后执行,先进后出
- 常用在函数退出或者异常情况发生时资源的释放和关闭
- 数据结构:延迟触发的链表
- 在函数退出之前执行,会预计算参数
panic与recover
- panic会停止当前函数的剩余代码,递归执行defer
- recover只有在defer函数中才可以捕获panic
- panic只会触发当前go routine的defer
select
- 同时监听多个文件描述符(channel)的可读和可写
- 打乱数组顺序(随机获取case)\
- 锁定所有channel\
- 遍历所有channel,判断是否有可读或者可写的,如果有,解锁channel,返回对应数据\
- 否则,判断有没有default,如果有,解锁channel,返回default对应scase\
- 否则,把当前groutian添加到所有channel的等待队列里,解锁所有channel,等待被唤醒\
- 被唤醒后,再次锁定所有channel\
- 遍历所有channel,把g从channel等待队列中移除,并找到可操作的channel\
- 如果对应的scase不为空,直接返回对应的值\
- 否则循环此过程
context
上下文 主要用于父子任务之间的同步取消信号,上游只是通知下游,但是不会直接干涉和中断下游任务的执行。
- context是一个接口,拥有四个方法 Err Deadline Done和Value
- 种类
- valueCtx 可以存储信息的ctx
- cancelCtx withCancel 会返回cancel方法,调用后Done方法会停止阻塞
- timerCtx withdeadline withtimeout 一段时间后停止阻塞
Golang 的错误
- error 具有 Error()方法的interface
- panic
- 需要recover才可以捕获掉,否则会导致程序的崩溃
- 主动调用panic函数
- 编译器触发
- 进程信号触发:非法地址访问
- fatal error
- 并发读写map
- 堆栈内存耗尽:stack overflow 死递归等
- nil函数作为go routine启动
- go routines死锁
- 线程耗尽
- OOM
dog fish cat
nil切片和空切片
- nil切片
- var a []int 引用数组指针地址为0
- 空切片
- b := []int{} 引用数组指针地址为一个非零的固定值
- 判断一个切片是否为空 不应该使用 == nil 应该使用len()==0
go routine 默认栈空间:2kb 最大 1g
go的调度模式
- GMP模型
- G:go routine 用户态的线程 数据结构:运行的函数指针,stack和上下文信息等。
- P:processor 默认为机器的核数,代表执行所需的资源
- M:对应一个内核线程
- 调度的过程就是P寻找M和G的过程
- 当p的本地g列满了的时候放到全局队列
- p没有本地队列的时候会从全局队列取g,还可能会发送work stealing
- 阻塞系统调用时,不在运行队列上,只是和M绑定
- golang最早的时候是gm模型,加p之后的变化
- 每个 P 有自己的本地队列,大幅度的减轻了对全局队列的直接依赖,所带来的效果就是锁竞争的减少。而 GM 模型的性能开销大头就是锁竞争。
- 当一个 M 中 运行的 G 发生阻塞性操作时,P 会重新选择一个 M,若没有 M 就新创建一个 M 来继续从 P 本地队列中取 G 来执行,提高运行效率。
- 每个 P 相对的平衡上,在 GMP 模型中也实现了 Work Stealing 算法,如果 P 的本地队列为空,则会从全局队列或其他 P 的本地队列中窃取可运行的 G 来运行,减少空转,提高了资源利用率。
- 调度的场景
- channel、mutex等sync操作发生了阻塞
- 调用time.sleep
- 进行系统调用
- 发生gc
- go routine运行过久
信号量
- 因为channel、mutex陷入阻塞的go routine,会调用gopark解除g和m的关联,g被封装成sudog节点,会根据mutex中的sema变量,调度到全局的平衡二叉树中保存。
- 解锁的时候会使用sema来唤醒go routine,会在全局的平衡二叉树中寻找当前mutex上阻塞的sudog,调用go ready函数
sync相关
waitgroup
- wg的方法:Add Done就是Add(-1), Wait
- 数据结构:state1 [3]uint32 sema waiter counter
- 用法:等待一组go routine的返回
- wait函数会在计数器大于0的时候陷入休眠
mutex 互斥锁
- 数据结构:state,sema int32 只占8个字节
- state:29位 waitersCount 等待的go routine个数 低三位:starving 是否进入饥饿状态 woken 从正常模式被唤醒 locked 锁定状态
- 正常模式与饥饿模式: 饥饿模式时会将锁直接移交给第一位等待者
- 自旋:保持cpu的占用,持续检查是否可以获取锁
- 进入自旋的条件:在普通模式下,多cpu,该go routine进入自旋的次数小于4次,有一个p且运行队列为空
- 获取锁的方式:CAS
- 如果当前go routine等待获取锁的时间超过了1ms,则进入饥饿状态
- 如果当前go routine是锁上最后一个等待的协程或者等待的时间小于1ms,切换成正常状态
- 解锁时,如果是在饥饿模式,将锁交给下一个等待者
- 如果是普通模式,如果有等待者,通过释放信号量唤醒对应的go routine
读写锁
- 写加锁:先禁止新进入的写 ,再禁止新进来的读
- 写解锁:先释放写锁,再释放读,防止一直在写,饿死读
- 读加锁
- 读解锁
go的内存分配机制
- go采用了分级分配机制,把对象分为了微对象,16B,小对象,32kb,大对象三种,
- 多级缓存,分为 线程缓存,中心缓存和页堆三个组件分级管理内存
- 线程缓存:属于一个独立的线程,分配的时候没有并发问题,遇到大对象直接在页堆分配
map
- map是无序的,在遍历前会随机选择一个地址开始遍历
- map的key必须是可以比较的
golang slice扩容规则
- Go1.17、1.18源码分析slice扩容规则
- runtime: make slice growth formula a bit smoother · golang/go@2dda92f (github.com)
- 从go 1.18开始变了
- 如果需要的容量是旧的两倍以上,就扩容到需要的容量。
- 如果小于两倍,1.18之前以1024作为分界,小于1024翻倍 大于则*1.25,1.5,1.75找最小满足要求的
- 1.18及以后是,小于256翻倍,大于256 newcap += (newcap + 3*256) / 4,扩容策略更加光滑
go routine什么时候会发生阻塞
- 由于原子操作,mutex、channel等调用导致go routine阻塞,调度器切换该g,从自己的
golang channel
go 的垃圾回收机制
- 三色标记法 黑色白色灰色
- 根对象标记为灰色
- 将灰色对象指向的对象变为灰色 自己变成黑色
- 直到不存在灰色对象,则垃圾回收结束
- 清理白色的垃圾对象即可
- 如果不stw,则可能有白色对象被黑色对象指向,然后白色对象被回收,造成内存异常
- 为了避免6的发生,也不产生stw,需要加入插入屏障和删除屏障
- 插入屏障:A对象引用B对象的时候,B被标记为灰色
- 删除屏障:被删除的对象如果是灰色或者白色,那么被标记为灰色。
字符串转byte数组 会不会有内存拷贝 怎么不发生
- 会有内存拷贝
- 可以使用unsafe ,不会发生内存拷贝