Golang相关

183 阅读8分钟

go的调度相关

  1. v1.1 非抢占式调度
  2. v1.2 基于协助的抢占式调度
  3. v1.14 基于信号的抢占式调度
    1. 协程运行超过20ms就会被强行夺取cpu运行权。

golang 内存分配

  1. 栈内存 由编译器自动分配和释放

    1. 初始值为2kb,函数调用之前会检查栈空间大小,扩容会扩大到原有的两倍,最大为1gb
    2. 缩容:当利用率小于1/4时进行缩容,缩为原有的1/2.
  2. 堆内存

  3. 变量的内存分配

    1. 局部变量由编译器控制,如果函数内的局部变量在外部仍需使用,则分配到堆上,没有使用则分配在栈上。

struct是否可以比较

  1. 不可以比较的类型:slice,map和function
  2. 其他的都可以比较
  3. 对于指针:两个空指针是相同的,指向同一个对象的指针也是相同的
  4. reflect.DeepEqual可以对两个对象进行比较

defer

  1. 在函数退出的时候执行,或者发生panic的时候执行,先加入defer的后执行,先进后出
  2. 常用在函数退出或者异常情况发生时资源的释放和关闭
  3. 数据结构:延迟触发的链表
  4. 在函数退出之前执行,会预计算参数

panic与recover

  • panic会停止当前函数的剩余代码,递归执行defer
  • recover只有在defer函数中才可以捕获panic
  • panic只会触发当前go routine的defer

select

  • 同时监听多个文件描述符(channel)的可读和可写
  1. 打乱数组顺序(随机获取case)\
  2. 锁定所有channel\
  3. 遍历所有channel,判断是否有可读或者可写的,如果有,解锁channel,返回对应数据\
  4. 否则,判断有没有default,如果有,解锁channel,返回default对应scase\
  5. 否则,把当前groutian添加到所有channel的等待队列里,解锁所有channel,等待被唤醒\
  6. 被唤醒后,再次锁定所有channel\
  7. 遍历所有channel,把g从channel等待队列中移除,并找到可操作的channel\
  8. 如果对应的scase不为空,直接返回对应的值\
  9. 否则循环此过程

context

上下文 主要用于父子任务之间的同步取消信号,上游只是通知下游,但是不会直接干涉和中断下游任务的执行。

  • context是一个接口,拥有四个方法 Err Deadline Done和Value
  • 种类
  • valueCtx 可以存储信息的ctx
  • cancelCtx withCancel 会返回cancel方法,调用后Done方法会停止阻塞
  • timerCtx withdeadline withtimeout 一段时间后停止阻塞

Golang 的错误

  1. error 具有 Error()方法的interface
  2. panic
    • 需要recover才可以捕获掉,否则会导致程序的崩溃
    • 主动调用panic函数
    • 编译器触发
    • 进程信号触发:非法地址访问
  3. 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的调度模式

  1. GMP模型
    1. G:go routine 用户态的线程 数据结构:运行的函数指针,stack和上下文信息等。
    2. P:processor 默认为机器的核数,代表执行所需的资源
    3. M:对应一个内核线程
  • 调度的过程就是P寻找M和G的过程
  • 当p的本地g列满了的时候放到全局队列
  • p没有本地队列的时候会从全局队列取g,还可能会发送work stealing
  • 阻塞系统调用时,不在运行队列上,只是和M绑定
  1. golang最早的时候是gm模型,加p之后的变化
    • 每个 P 有自己的本地队列,大幅度的减轻了对全局队列的直接依赖,所带来的效果就是锁竞争的减少。而 GM 模型的性能开销大头就是锁竞争。
    • 当一个 M 中 运行的 G 发生阻塞性操作时,P 会重新选择一个 M,若没有 M 就新创建一个 M 来继续从 P 本地队列中取 G 来执行,提高运行效率。
    • 每个 P 相对的平衡上,在 GMP 模型中也实现了 Work Stealing 算法,如果 P 的本地队列为空,则会从全局队列或其他 P 的本地队列中窃取可运行的 G 来运行,减少空转,提高了资源利用率。
  2. 调度的场景
  • channel、mutex等sync操作发生了阻塞
  • 调用time.sleep
  • 进行系统调用
  • 发生gc
  • go routine运行过久

信号量

  1. 因为channel、mutex陷入阻塞的go routine,会调用gopark解除g和m的关联,g被封装成sudog节点,会根据mutex中的sema变量,调度到全局的平衡二叉树中保存。
  2. 解锁的时候会使用sema来唤醒go routine,会在全局的平衡二叉树中寻找当前mutex上阻塞的sudog,调用go ready函数

sync相关

waitgroup

  1. wg的方法:Add Done就是Add(-1), Wait
  2. 数据结构:state1 [3]uint32 sema waiter counter
  3. 用法:等待一组go routine的返回
  4. wait函数会在计数器大于0的时候陷入休眠

mutex 互斥锁

  1. 数据结构:state,sema int32 只占8个字节
  2. state:29位 waitersCount 等待的go routine个数 低三位:starving 是否进入饥饿状态 woken 从正常模式被唤醒 locked 锁定状态
  3. 正常模式与饥饿模式: 饥饿模式时会将锁直接移交给第一位等待者
  4. 自旋:保持cpu的占用,持续检查是否可以获取锁
  5. 进入自旋的条件:在普通模式下,多cpu,该go routine进入自旋的次数小于4次,有一个p且运行队列为空
  6. 获取锁的方式:CAS
  7. 如果当前go routine等待获取锁的时间超过了1ms,则进入饥饿状态
  8. 如果当前go routine是锁上最后一个等待的协程或者等待的时间小于1ms,切换成正常状态
  9. 解锁时,如果是在饥饿模式,将锁交给下一个等待者
  10. 如果是普通模式,如果有等待者,通过释放信号量唤醒对应的go routine

读写锁

  1. 写加锁:先禁止新进入的写 ,再禁止新进来的读
  2. 写解锁:先释放写锁,再释放读,防止一直在写,饿死读
  3. 读加锁
  4. 读解锁

go的内存分配机制

  1. go采用了分级分配机制,把对象分为了微对象,16B,小对象,32kb,大对象三种,
  2. 多级缓存,分为 线程缓存,中心缓存和页堆三个组件分级管理内存
  3. 线程缓存:属于一个独立的线程,分配的时候没有并发问题,遇到大对象直接在页堆分配

map

  1. map是无序的,在遍历前会随机选择一个地址开始遍历
  2. map的key必须是可以比较的

golang slice扩容规则

  1. Go1.17、1.18源码分析slice扩容规则
  2. runtime: make slice growth formula a bit smoother · golang/go@2dda92f (github.com)
  3. 从go 1.18开始变了
  4. 如果需要的容量是旧的两倍以上,就扩容到需要的容量。
  5. 如果小于两倍,1.18之前以1024作为分界,小于1024翻倍 大于则*1.25,1.5,1.75找最小满足要求的
  6. 1.18及以后是,小于256翻倍,大于256 newcap += (newcap + 3*256) / 4,扩容策略更加光滑

go routine什么时候会发生阻塞

  1. 由于原子操作,mutex、channel等调用导致go routine阻塞,调度器切换该g,从自己的

golang channel

go 的垃圾回收机制

  1. 三色标记法 黑色白色灰色
  2. 根对象标记为灰色
  3. 将灰色对象指向的对象变为灰色 自己变成黑色
  4. 直到不存在灰色对象,则垃圾回收结束
  5. 清理白色的垃圾对象即可
  6. 如果不stw,则可能有白色对象被黑色对象指向,然后白色对象被回收,造成内存异常
  7. 为了避免6的发生,也不产生stw,需要加入插入屏障和删除屏障
  8. 插入屏障:A对象引用B对象的时候,B被标记为灰色
  9. 删除屏障:被删除的对象如果是灰色或者白色,那么被标记为灰色。

字符串转byte数组 会不会有内存拷贝 怎么不发生

  1. 会有内存拷贝
  2. 可以使用unsafe ,不会发生内存拷贝