GO常见高频面试题(GMP、三色标记)

前言

想学习数据库面试知识点的朋友,关注狗蛋儿,后续相关内容更新。大家都是苦逼程序员,如果能帮到朋友们,我自然非常乐意。

文章目录

  • 前言
  • Channel
    • channel为nil或关闭时,读写会怎么样?
    • channel 什么时候会阻塞?
    • channel 怎么实现无阻塞?
  • init函数
    • init函数在一个GO文件的个数?
    • 多个init函数的执行顺序?
    • init和main函数可以共存吗?
    • 可以含有返回值和参数吗?
  • 引用类型有哪些?
  • 序列化
    • 不能序列化的类型有哪些?
    • 结构体存在complex、channel、function,序列化时就会产生错误?
  • select
    • select只能处理IO操作吗?
    • select需添加条件判断?
    • select的执行顺序?是否阻塞?
  • switch 必须添加条件判断吗?
  • 结构体作为接受者,和结构体指针作为接受者,有什么不同?
  • cap适用哪些类型?适用map吗,表示什么?
  • i++和++i的区别?
  • GO可以对指针进行哪些操作?
  • 可以使用 := 定义全局变量?
  • 什么是 fallthrough
  • 通过range修改slice/map元素的值是否合理?
  • 下面代码能否遍历输出?
  • 协程panic后的过程是怎么样?
  • make
    • make channel/slice/map都必须指定长度?
    • make、new、var 区别?
  • slice
    • 空切片能否append?
    • 扩容机制?
    • 切片和数组的区别
  • map
    • 包含那几个重要结构体?
      • hmap
      • bmap
    • 添加流程?
    • 获取流程?
    • map未初始化时可以进行增删查操作吗?
  • channel 缓存的实现?
  • 协程并发
    • 什么是GMP?
    • GMP调度流程?
    • 五IO模型(非面试热点)
    • 什么是sysmon?有什么功能?
  • 垃圾回收
    • 根对象
    • 简述三色标记法流程
    • 什么STW?
    • 一定需要STW?--屏障机制
    • 插入屏障
    • 删除屏障
    • 混合屏障

Channel

channel为nil或关闭时,读写会怎么样?

  • channel 为 nil 时读:永久阻塞。
  • channel 为 nil 时写:永久阻塞。
  • channel 关闭时写:触发 panic。
  • channel 关闭时读:返回nil。

channel 什么时候会阻塞?

  • channel为nil时,读写都会阻塞。
  • channel 无数据,读会阻塞。
  • channel 缓存buffer占满,写会阻塞。

channel 怎么实现无阻塞?

利用select的default语句。

init函数

init函数在一个GO文件的个数?

一个文件可以有0个、1个或多个。

多个init函数的执行顺序?

同文件中从上到下顺序执行。如有引用文件,先执行引用文件 init 函数。

init和main函数可以共存吗?

可同时存在,init函数执行完后再执行 mian 函数。

可以含有返回值和参数吗?

不能,main函数也不能。

引用类型有哪些?

指针、channel、接口、切片、map、函数。

序列化

不能序列化的类型有哪些?

complex、channel、function

结构体存在complex、channel、function,序列化时就会产生错误?

不一定,如果这类成员变量是小写字母开头,其将不会进行序列化,就不会报错。

select

select只能处理IO操作吗?

是的,它只能处理IO操作,每个case后必须是IO操作。

select需添加条件判断?

select后不跟条件判断。不同于switch,switch可以跟也可以不跟

select的执行顺序?是否阻塞?

case随机执行。

  • 如果没有case收到消息,且没有default语句,便会阻塞直到收到channel消息。
  • 如果没有case收到消息,有default语句,便执行default语句后跳出select,不会阻塞。

switch 必须添加条件判断吗?

  • switch 后可以有判断条件,也可以没有。这和Java和C++有所不同。

结构体作为接受者,和结构体指针作为接受者,有什么不同?

  • 结构体作为接受者,当为接口类型变量赋值时,无论结构体初始化变量,还是结构体指针初始化变量都可以编译通过。
  • 结构体指针作为接受者,当为接口类型变量赋值时,只能结构体指针初始化变量才能编译通过。
type Wish interface {
    GetGirlFriend(number int)
}
type Aladdin struct {}

func (Aladdin) GetGirlFriend(number int) {
    ...
}

var wish Wish = new(Aladdin) // 正确
var wish Wish = Aladdin{}    // 正确
var wish Wish = &Aladdin{}   // 正确,上述是通过结构体作为接受者,所以初始化变量时,使用指针也没问题

cap适用哪些类型?适用map吗,表示什么?

cap使用array、slice、channel。

  • array:查看数组元素个数,cap和len相同。
  • slice:查看最大容量。len查看元素个数。
  • channel:查看buffer最大容量。len查看元素个数。
  • map:不适用,可以使用len()获取大小

i++和++i的区别?

GO没有++i,只有后置操作符i++。并且其没有返回值,即result := i++也是错误的。

GO可以对指针进行哪些操作?

  • 取地址&
  • 取指针执行的数据*
  • 不能对指针进行自增自减操作。

可以使用 := 定义全局变量?

不能,只能使用var

var str string
var str = ""
stri := "" //错误

什么是 fallthrough

GO switch语法中使用,当没有fallthrough语句,case执行完后会默认break跳出switch语句;当含有fallthrough语句,则不会跳出并继续进入下个case执行。

通过range修改slice/map元素的值是否合理?

不合理,range是值传递,修改没有效果。而在java里“更像是引用传递”,修改有效果。

下面代码能否遍历输出?

不能,闭包引用了非参数的值,都是引用传递。所以下面代码中,协程使用的是元素的引用,而这个元素值在随着range的遍历在不断变化,协程到底使用的哪个值便无法预估了。

str := []string{"I","am","Sergey"}
for _,v := range str{
    go func() {
        fmt.Println(v)
    }()
}
time.Sleep(1 * time.Second)
// 程序打印出“Sergey”、“Sergey”、“Sergey”。
// v的值在随着range发生变化(类似迭代器),而传递的又是引用,当第一个协程开始打印的时候,v已经变换为指向了最后一个元素。

协程panic后的过程是怎么样?

当前调用栈函数终止运行,调用defer函数(函数中存在多个defer函数按后入先出执行),然后按照函数调用栈向上传递,如果没有遇到recover,最终到达main函数终止,然后程序崩溃,程序中其它协程无法执行defer函数,也无法recover该panic。

make

make channel/slice/map都必须指定长度?

只有slice必须指定长度。其它两种未指定长度默认长度为0。

make、new、var 区别?

  • make只能对slice、channel、map操作,会进行初始化,比如初始化长度、容量和为每个成员赋初值,并返回初始化的值。
  • new可对所有类型操作,用来分配空间,并不会进行初始化,返回指针。实际上new和var没有任何区别,都不会初始化,返回的是变量的零值,只是new得到变量地址,var得到变量值。

slice

空切片能否append?

可以。

扩容机制?

容量未到1024时,按照翻倍扩容,超过1024时,每次扩容四分之一左右。

切片和数组的区别

  • 数组必须指定长度,切片长度不定。
  • 在作为形参时,数组为值传递,切片效果像是引用传递(实际为值传递)。

map

包含那几个重要结构体?

hmap

  • B:决定桶的个数,也就是bmap的个数,个数为2^B。
  • count:size of map。
  • buckets:指针数组,每个指针指向一个 bmap。

bmap

桶,存放key/value的结构体。

  • topbits:数组,长度为8位。存放 hash 前8位。
  • keys/values:分别对应两个数组,长度为8。存放键值对。
  • overflow:桶中存放的键值对超过8个时,新加入该桶的键值对放到overflow中。这和java不同,java如果超过8个,由链表变成红黑树。

添加流程?

  • 判断哪个桶:通过hash值的后五位确定哪个桶。
  • 判读桶内是否存在 key:遍历比较加入元素的hash前8位和topbits是否相等,如果不等则 key 一定不等,便continue继续后续遍历;若相等也不能确定 key 值一定相等,需取 key 进行值比较,若真相等则覆盖,不等则继续遍历;若遍历完了没有找到相同 key,则将key/value加入对应数组中,topbit 设置为 hash 前8位。(上述遍历,包含有 overflow 的遍历)

获取流程?

判断 key 是否存在的方式和上述一样。找到了则取出,否则返回value类型的零值

map未初始化时可以进行增删查操作吗?

可以进行删、查。不能进行增。

channel 缓存的实现?

环形缓冲区,recvx表示读取的位置,sendx表示写入的位置。超出环形缓冲区的元素,使用双向链表存储。

协程并发

什么是GMP?

  • G:goroutine,协程。
  • M:machine,内核线程的封装。
  • P:processor,管理G和M之间调度等关联关系。每个P需要绑定一个M,由于P将G交给M执行。

GMP调度流程?

  • P有一个G的局部队列,保存着等待M执行的G。当P局部队列已满,便将G放入全局队列。
  • M绑定的P的局部队列为空时,M便会去全局队列中取G来执行。如果全局队列为空,M回去其它P局部队列中偷取G来执行。
  • G 因系统调用阻塞时,会阻塞M,此时P会和M解绑即hand off,P去寻中空闲的M,如果没有,创建一个新的M。
  • G 因IO阻塞,不会阻塞M,M继续运行其它的G,阻塞的G恢复后重新进入P局部队列。

五IO模型(非面试热点)

  • 阻塞:数据未就绪,原线程阻塞挂起让出cpu,等待数据就绪后,内核唤醒线程,线程进入等待队列,等待cpu调度执行。
  • 非阻塞:数据未就绪,函数直接返回错误码,原线程继续执行不会暂停挂起。
  • IO 复用:select\epoll,也是阻塞的,只是循环监听着众多文件描述符。
  • 信号驱动 IO:数据未就绪,原线程继续执行不会暂停挂起,数据就绪后内核会发送SIGIO信号,由处理SIGIO信号的函数进行后续操作。
  • 异步IO:数据未就绪,原线程继续执行不会暂停挂起,等数据就绪后,内核调用回调函数(io 的相关系统调用,可以设置回调函数)。
    所以很明显是没有什么异步非阻塞的,真实的io模型就只有这五种。

什么是sysmon?有什么功能?

类似Linux的定时线程,go语言本身也有一个监护线程,由于它本身是个线程,便不需要绑定P。

  • 释放闲置5分钟的内存
  • 超过2分钟没有垃圾回收,强制垃圾回收。
  • 将长时间未处理的netpoll添加到全局队列。
  • 向长时间运行的G发出抢占调度。
  • 收回系统调用长时间阻塞的G。

垃圾回收

根对象

垃圾回收之前,首先了解go中根对象指的什么:全局对象、栈中局部变量。
当然在java中根对象复杂一些,包含:栈中对象、方法区静态对象、方法区常量对象、native 对象。

简述三色标记法流程

三色标记实际是通过标记对象为三种不同状态,来对对象进行回收。

  1. 所有对象都标记为白色。
  2. 每次GC从根节点遍历对象,把遍历到的对象放入灰色区。
  3. 遍历灰色对象,将灰色对象引用到的白色对象,加入灰色区,将原灰色对象本身放入黑色区。
  4. 重复第三步,直到没有灰色对象。
  5. 清除白色对象,因为此时白色对象不属于根对象,也没被其它对象引用,属于垃圾可以回收。

什么STW?

stop the world,指垃圾回收时,会暂定其它线程。
避免运行的线程改变对象的引用关系,说白了就是解决同步问题,比如:回收过程中,白对象和灰对象的引用断开,却建立了和黑对象的引用关系,这时按照三色标记法回收垃圾,白对象会被回收,导致程序出错。

一定需要STW?–屏障机制

不是,GO中通过屏障机制避免了STW

插入屏障

垃圾回收期间,将新建引用的对象标记为灰色。

避免,白对象建立黑对象引用后,白对象仍被误删。

因为栈和寄存器不能hook,所以栈没有插入屏障机制,那么栈的垃圾回收仍需要STW。

删除屏障

垃圾回收期间,引用被删除的对象标记为灰色。

避免,白对象和灰对象引用被删除,但立刻新建立了和黑对象的引用关系后,白色对象仍被误删。

但是这样做精度降低了,也就是在这一轮遍历回收中,即便对象所有引用都解除了,它也只会被放到下一轮回收才被清理。
注意,插入和删除屏障都是写屏障哈,读屏障没涉及

混合屏障

插入和删除屏障的结合,同时赋值器也变成了黑色赋值器,见下文。无需STW,但也存在精度问题,它的流程如下:

  1. GC开始将栈上的对象全部扫描并标记为黑色。
  2. GC期间,任何在栈上创建的新对象,均为黑色。
  3. 被删除的对象标记为灰色。
  4. 被添加的对象标记为灰色。

你可能感兴趣的:(Golang,面试,golang,职场和发展)