目录

go知识点梳理

map [[2023-02-20 Mon]] #

最主要的数据结构有两种:哈希查找表(Hash table)搜索树(Search tree) avl或rb树。 Go 语言采用的是链表解决哈希冲突。 1.9.2

map 内存模型 #
  • 【引申1】slice 和 map 分别作为函数参数时有什么区别? – slice返回的是结构体 map是指针
  • bmap 就是我们常说的“桶”,桶里面会最多装 8 个 key,这些 key 之所以会落入同一个桶,第九个就会新加通过overflow连起来
  • 每个bucket内部key 和value是分开放的,不是一组一组放
  • map在扩容的时候,要搬迁部分bucket,可能存在中间状态, 什么时候扩容? 壮哉因子超过 阈值6.5, overflow的bucket过多
  • key为什么是无序的? 扩容后会搬,key位置会变动很大; 还一个每次for 遍历时go也是从一个随机序号开始的
  • map不是线程安全的、遍历过程删除加都可能有问题 sync.RWMutex
  • 无法对map的key value取地址,即时通过unsafe.pointer拿到,也可能会变了
interface#
  • go的接口有什么特点: 引入了不要求类型显示地声明实现了某个接口,只要实现了相关的方法即可

  • ifaceeface 都是 Go 中描述接口的底层结构体,区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}

  • iface 内部维护两个指针,tab 指向一个 itab 实体 data指向具体的值

  • tab 是接口表指针,指向类型信息;data 是数据指针,则指向具体的数据。它们分别被称为动态类型动态值。而接口值包括动态类型动态值 #

  • eface 就比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值

  • 编译期自动检测是否实现了接口 #

  • 1
    2
    
      var _ io.Writer = (*myWriter)(nil)
      var _ io.Writer = myWriter{}     // 检查 myWriter 类型是否实现了 io.Writer 接口
    

类型转换和断言区别

  • 类型转换类型断言本质都是把一个类型转换成另外一个类型。不同之处在于,类型断言是对接口变量进行的操作

Go接口和c++接口有和异同 #

  • C++ 通过虚函数表来实现基类调用派生类的函数;而 Go 通过 itab 中的 fun 字段来实现接口变量调用实体类型的函数

channel #
  • 不要通过共享内存来通信,而要通过通信来实现内存共享。 这就是 Go 的并发哲学,它依赖 CSP 模型,基于 channel 实现 #

  • 数据结构 #

  • buf 指向底层循环数组(缓冲channel), sendq recvq 表示被阻塞的goroutine, sendx recvx 发送接收msg相对位置

向channel发送数据过程怎么样的?

  • a.检查接收的channel是否合法、比如nil直接返回fasle,goroutine 挂起 b.对于不阻塞的发送操作,如果 channel 未关闭并且没有多余的缓冲空间

  • 如果channel已close,panic

  • 如果能从等待接收队列 recvq 里出队一个 sudog(代表一个 goroutine),说明此时 channel 是空的,没有元素,所以才会有等待接收者。这时会调用 send 函数将元素直接从发送者的栈拷贝到接收者的栈,关键操作由 sendDirect 函数完成

  • 如果没有命中以上条件的,说明 channel 已经满了,阻塞起来。

  • 这里有一些绑定操作,sudog 通过 g 字段绑定 goroutine,而 goroutine 通过 waiting 绑定 sudog,sudog 还通过 elem 字段绑定待发送元素的地址,以及 c 字段绑定被“坑”在此处的 channel。

  • 所以,待发送的元素地址其实是存储在 sudog 结构体里,也就是当前 goroutine 里。

从channel读取数据怎么样的?

  • 如果 channel 是一个空值(nil),在非阻塞模式下,会直接返回
  • 和发送函数一样,接下来搞了一个在非阻塞模式下,不用获取锁,快速检测到失败并且返回的操作。 (没准备好: 非缓冲的发送队列里没goroutine,缓冲性buffer是空的 — > 都说明没数据可操作)
  • 接下来的操作,首先会上一把锁,粒度比较大。
  • 接下来,如果有等待发送的队列,说明 channel 已经满了,要么是非缓冲型的 channel,要么是缓冲型的 channel,但 buf 满了。这两种情况下都可以正常接收数据。
  • 最后,取出 sudog 里的 goroutine,调用 goready 将其状态改成 “runnable”,待发送者被唤醒,等待调度器的调度。

关闭goroutine过程

操作channel总结

  • 总结一下,发生 panic 的情况有三种:向一个关闭的 channel 进行写操作;关闭一个 nil 的 channel;重复关闭一个 channel。
  • 读、写一个 nil channel 都会被阻塞

如何优雅关闭channel

  • 有两个不那么优雅地关闭 channel 的方法:

  • 使用 defer-recover 机制,放心大胆地关闭 channel 或者向 channel 发送数据。即使发生了 panic,有 defer-recover 在兜底。

  • 使用 sync.Once 来保证只关闭一次。

  • 引入一个中间者: channel 通知goroutine关闭,最终有一个主close

channel泄露

  • 泄漏的原因是 goroutine 操作 channel 后,处于发送或接收阻塞状态,而 channel 处于满或空的状态,一直得不到改变。同时,垃圾回收器也不会回收此类资源,进而导致 gouroutine 会一直处于等待队列中,不见天日。
  • 另外,程序运行过程中,对于一个 channel,如果没有任何 goroutine 引用了,gc 会对其进行回收操作,不会引起内存泄漏
channel引用有哪些? #
  • 停止信号
  • 任务定时 #
  • 解耦生产方和消费方 #
  • 控制并发数 #
context #
GC

1.基础

2. 根对象到底是什么?#
  • 根对象 在垃圾回收的术语中又叫做根集合,它是垃圾回收器在标记过程时最先检查的对象,包括:

  • 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。

  • 执行栈:每个 goroutine 都包含自己的执行栈,这些执行栈上包含指向分配的堆内存区块的指针

  • 寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块。

3. 常见的 GC 实现方式有哪些?Go 语言的 GC 使用的是什么?#
  • 算法 其存在形式可以归结为追踪(Tracing)和引用计数(Reference Counting)这两种形式的混合运用,

  • [[追踪式GC]]

  • 特点 Go 的 GC 目前使用的是无分代(对象没有代际之分)、不整理(回收过程中不对对象进行移动与整理)、并发(与用户代码并发执行)的三色标记清扫算法、

  • 从根对象出发,根据对象之间的引用信息,一步步推进直到扫描完毕整个堆并确定需要保留的对象,从而回收所有可回收的对象。Go、 Java、V8 对 JavaScript 的实现等均为追踪式 GC

  • 引用计数式GC

  • 每个对象自身包含一个被引用的计数器,当计数器归零时自动得到回收。因为此方法缺陷较多,在追求高性能时通常不被应用。Python、Objective-C 等均为引用计数式 GC, 快简单、但每次要计算引用数性能差,需要额外内存记录引用数

4. 三色标记法是什么?#
  • 关键 理解对象的三色抽象以及波面(wavefront)推进这两个概念。三色抽象只是一种描述追踪式回收器的方法,

  • 白色:: 要被回收的对象,不可达的

  • 灰色:: 中间带的,可能被回收,也可能可达对象

  • 黑色:: 就是可达的,不可回收的

5. STW 是什么意思? #
  • 定义:: stop the world,开始gc的时候会停止所有用户代码,导致停止
6. 如何观察 Go GC?#
  • 方式1: GODEBUG=gctrace=1 #

  • 方式2..4: go tool、 runtime.memstate、 gcstate信息

  • wall clock 是指开始执行到完成所经历的实际时间,包括其他程序和本程序所消耗的时间; cpu time 是指特定程序使用 CPU 的时间

  • wall clock < cpu time: {{cloze 充分利用多核}}

  • wall clock ≈ cpu time: 未并行执行

  • wall clock > cpu time: 多核优势不明显

7. 有了 GC,为什么还会发生内存泄露?#
  • 严谨的话来说应该 预期的能很快被释放的内存由于附着在了长期存活的内存上、或生命期意外地被延长,导致预计能够立即回收的内存而长时间得不到回收

  • a. 比如把局部变量附着到了全局的变量cache上去了,无法释放

  • b. goroutine leak

  • 例:: 一个 goroutine 尝试向一个没有接收方的无缓冲 channel 发送消息,则该 goroutine 会被永久的休眠,整个 goroutine 及其执行栈都得不到释放

8. 并发标记清除法的难点是什么? #
  • 如何保证标记与清除的准确性
9. 什么是写屏障、混合写屏障,如何实现?#

2.GC 的实现细节 #

10. Go 语言中 GC 的流程是什么? #
11. 触发 GC 的时机是什么? #
  • 主动触发

  • 被动触发,分为两种方式:

  • 使用系统监控,当超过两分钟没有产生任何 GC 时,强制触发 GC。

  • 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。

12. 如果内存分配速度超过了标记清除的速度怎么办? #
  • 简单说就是当gc发生时,会标记,让内存分配慢点,达到回收和分配平衡

  • 当 GC 触发后,会首先进入并发标记的阶段。并发标记会设置一个标志,并在 mallocgc 调用时进行检查。当存在新的内存分配时,会暂停分配内存过快的那些 goroutine
3.GC 的优化问题 #
13. GC 关注的指标有哪些?#
  • cpu利用率:: gc会影响多大 GC停顿时间: stw gc停顿频率:: 造成程序停顿频率
14. Go 的 GC 如何调优? #
  • Go 的 GC 被设计为极致简洁,与较为成熟的 Java GC 的数十个可控参数相比,严格意义上来讲,Go 可供用户调整的参数只有 GOGC 环境变量

  • 一般其实不需要care这些问题:: 只有对执行延迟非常敏感才需要,gc的开销影响了性能;无非:尽量少申请内存,复用

  • ((63f859a9-3219-4740-897a-774254c56dd9))

GC

1.基础

2. 根对象到底是什么?#
2. 根对象到底是什么?#
2. 根对象到底是什么?#