Go面试(进阶装逼版)

302 阅读4分钟

这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战

Go

核心特性

goroutine

channel

defer

切片

垃圾回收机制(内置runtime?)

int与int32

interface代替继承多态

字母大小设置可见性

没有模板泛型

所有类型初始化默认为0

反射

切片Slice与数组

在底层实现上:

  • 切片,是底层实现数组每个数据的引用,自身是结构体,\

    • 初始化时,可以不进行长度、容量的声明,长度可以不断增加,但是不会减少\

      • 长度为slice的元素个数
      • 容量是slice第一个元素到底层数组的最后一个最后一个元素的距离
    • 长度可变
    • 底层连续地址空间的引用、指针
    • 作为函数参数而言,和数组也是不一样的
  • 数组,为底层数组的值传递,在for循环的时候会有一个临时对象\

    • 初始化的时候,需要确定数组的长度
    • 长度固定
    • 值传递
    • 作为参数传递的时候,函数参数类型也需要制定数组长度 初始化方式: 数组:
var test [len]type
var test [...]type{value0, value1, ......}
var test [N]type{value0,......,valueN-1}
// 以及上述两种的组合

切片:

test := make([]type, len, cap)
test := make([]type, len)
test := []type{}
test := []type{value0, ......}

make与new

  • new用于各种类型的内存分配,返回指针,指向对应类型的零值
    • 类型的指针需要分配内存才能赋值,与c中一样
  • make只用于给slice、map、channel类(引用类型)分配内存,返回对应的类型,并非指针

结构体细节

并发编程

runtime

go运行的基础设施

  • 协调多线程调度、内存分配、GC
  • 操作系统、CPU级别操作的封装(信号处理,系统调用、寄存器操作、原子操作)、CGO
  • 性能分析等,pprof、trace、race
  • 反射实现

与python、java的runtime不同的是,在运行时与用户代码没有明显的界限,一起打包

最主要的功能是两个方面:调度、GC

调度

众所周知,操作系统内部有着线程、进程调度器,当触发阻塞、时间片用尽、硬件中断的时候,都会涉及到切换的问题 Go在此基础上实现了自己的调度器(调度Goroutine,Go内部最基本的执行单元)

线程与协程

线程:共享堆,不共享栈,其切换由操作系统控制 协程:共享堆,不共享栈,切换由程序员显式控制,可以避免上下文切换的额外消耗,可以运行在一个或多个线程上 协程的切换调度在用户空间完成,不涉及到用户空间到内核空间的切换(寄存器切换、内存数据切换、栈切换、安全检查),线程调度里面的taskstructure除了CPU信息之外,还会保存线程的私有栈以及寄存器,上下文会多一点,在POSIX中线程获得了许多进程拥有的功能,这些功能在go的调度中都是用不到的,同时也增加了开销 由于线程拥有协程,而一个线程只在一个CPU上执行,导致协程没有办法利用多核 Goroutine与协程类似,且可以实现并行 Go实现了调度器之后

调度模型

在这里P记录了G队列以及对应线程的上下文信息、mcache,这些东西没有强制与M绑定 每个P都有独立的队列的同时,同样存在全局的G队列,会定时进行扫描分配,以防饿死 当前M产生的G会进入当前P的队列中

  • 队列的独立可以减少全局队列的锁竞争,提高效率,不会被原子操作等降低效率
  • 产生的G进入当前队列,不会进入其他的M,没有上下文切换的代价,提高亲和性
  • P与M独立,可以提高mecache的利用率,让阻塞G占用的mecache让出来

G状态流转

实际上并没有一个专门的线程或者进程作为一直运行的调度器实体,只有在协程状态变更、m操作等主动变化的时候才会进行调度

sysmon协程

类似linux内部的系统线程,作为一个常态协程,执行一些定时任务

  • 在网络交互netpoll中获取ready的协程G
  • 通知抢占等
  • GC
  • debug信息追踪

网络交互

GC

三大算法:Mark-Sweep、Mark-Sweep-Compact、Mark-Copy,都需要STW(stop the world) Go GC特征:三色标记、并发标记和清扫、非分代、非紧缩、混合写屏障

三色标记法