golang面试题总结1

462 阅读9分钟

make和new的区别是什么?

new
var sum *int // var sum int 的指针指向的内存地址是 0x0
sum = new(int) // sum new 之后的指针指向的内存地址是 0xc000018098
*sum = 99 // 对*sum 赋值后的指针指向的内存地址是 0xc000018098%!(EXTRA int=99) 如果不对sum进行new(int)则指针的内存地址为空 不能对*sum直接赋值

make
make 函数只用于 map,slice 和 channel,并且不返回指针。如果想要获得一个显式的指针,可以使用 new 函数进行分配,或者显式地使用一个变量的地址。

Go语言中的 newmake 主要区别如下:
-   make 只能用来分配及初始化类型为 slice、mapchan 的数据。new 可以分配任意类型的数据;
-   new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
-   new 分配的空间被清零。make 分配空间后,会进行初始化;

了解过golang的内存管理吗?

了解过,首先说一下虚拟内存
虚拟内存是通过页和页表来存储的,大致如下图,程序一般不会直接操作内存而是通过操作虚拟内存来拿到数据
每页中都是以数组的方式进行存储的,包含有效位(是否被缓存在内存中或者cpu中)和物理磁盘位置以及下标

局部性原理:可能会被问到,由于内存空间都是连续的,在读写操作时,比如for循环写入到变量中,如果数组的下表不是连续的则效率会变低
当前golang语言由于内置内存管理,所以不需要自己去实现内存管理如果需要实现内存管理可以通过cGo这个包来操作C语言来进行内存管理

golang内存采用了TCMallocTC麦老客 读法)算法
大概需要掌握几个基本的概念
golang调度器 简称P
golang内核线程 简称M
golang协程 简称G
每个调度器都会和一个内核线程做绑定(PMG算法)通过P来调度G去M中执行 ,PMG算法执行流程如下图

image.png

那么golang的内存管理是如何实现的呢?
通过PMG模型,Mcache是和P直接绑定的协程逻辑层从mcache上获取内存是不需要加锁的,因为一个P只有一个M在其上运行,不可能出现竞争,由于没有锁限制,mcache则其到了加速内存分配。

首先也先需要了解几个名词
Mcache:每个线程M特有的内存块(线程访问时不需要加锁)
  由多个SpanClass组成,每个spanClass都会对应一个级别,不同级别决定了mspan下object的大小
  比如1级别每个object的大小是8b那么1页是8kb则1个page可以存放 (1024*8)/8 = 1024个 object,每个object都是双向链表,每个mspan也是一个双向链表
  比如2级别每个object的大小是16b那么1页是8kb则1个page可以存放 (1024*8)/16 = 512个 object,每个object都是双向链表,每个mspan也是一个双向链表
  每个spanClass下会有两个mSpan 1个是带指针引用的,一个是不带指针引用的,带指针引用的需要gc,不带指针引用的不需要gc
  举例如图1
MCentral:所有线程的共享内存(访问时需要加锁)
当Mcache中某个级别的mspan被程序占用为空时,mcache就会向MCentral中,借内存放在自己的链表尾部,当Mcache有空闲内存时,在还给Mcentral。
这里需要注意一下不同级别的内存之间交换的单位是不一样的,程序的最小单位是object,所以程序在和Mcache交互时是object,而McacheMCentral交换的单位为span,McentralHpage交换的单位是page

MHelp:虚拟内存,直接和操作系统进行交互
  当MCentral中内存不够时,会像MHelp寻求帮助,MHelp会有多个Arenas,每个Arenas下会存在多个page页,每个Arenas都会有一个bigMap记录当前内存是否被使用是否可以扩容等
  
golang是如何进行内存分配的?
  tiny对象
  申请内存大小<=16b的对象会被认为是tiny对象,如果不包含指针则会放在内存缓冲区,如果包含指针则会将申请一个16b大小的span放入缓冲区,采用字段对齐的方式
  
  小对象
  如果申请内存大小超过16b 并且小于 32kb 通过级别来判断改向那个spanSize申请mspan,并且判断是否有指针如果有指针则需要gc没有则不需要gc可能会写入不同的mspan

  大对象
  如果程序申请内存超过32kb则直接绕过MCache来申请内存直接向MHelp来申请内存,MHelp判断申请内存大小来向HHelpArenas申请相应的pages
  

图1 image.png 小对象申请内存图 image.png 大对象申请内存图 image.png

调用函数传入结构体时,应该传值还是指针﹖说出你的理由?

传值会减少内存逃逸,减少GC的压力
  什么情况下会内存逃逸呢?内存逃逸有什么影响呢?
  当指针当作返回值的之后会发生内存逃逸,会从栈上逃逸到堆上
  主要影响就是增加gc的压力
  堆和栈的区别是什么呢?
  栈主要在函数内分配内存,堆的生命周期比较长,比如说a引用b,在b内声明了一个指针变量那么此变量就会被分配到栈上,如果此变量成为函数的返回值那么此变量就会逃逸到堆上,由于堆内存不能自动回收需要gc来回收所以成本比较高
传指针可能会提升性能,比如一个比较大的结构体传入指针会减少值拷贝的一个开销

线程有几种模型?Goroutine的原理了解过吗,讲一下实现和优势?

1:1 1个用户线程在1个内核线程上跑
N:1 多个用户线程在1个内核线程上跑
M:N 多个用户线程多个内核线程上跑

goroutine的原理实际上就是 go程序启动时会启动多个go程序调度器,每个调度器会绑定一个内核线程,有调度器协调go程序区内核线程上跑

优势:
1、每个goroutine只需要2-4kb 开销比较小
2、调度方便 成本低
3、每个p都有自己的内存,不需要加锁

Goroutine什么时候会发生阻塞?

当go程序 发生 systemcall的时候会产生goroutine阻塞,阻塞时P(带着P队列中的go程序)会和当前M解绑同时唤醒或者重新生成一个M区绑定,当前正在执行的程序会放入全局队列当中

PMG模型中Goroutine有哪几种状态?

1、没有被初始化
2、等待运行中
3、运行中
4、systemcall 阻塞
5、阻塞后在全局队列、或者发送到其他p的过程中

每个线程/协程占用多少内存知道吗?

线程2M左右
一个协程大概2K左右

如果Goroutine—直占用资源怎么办,PMG模型怎么解决的这个问题?

当前m会保留当前g P会和m解绑带领着p队列中的G和其他M绑定
当前G和M退出系统调用后,G和M会尝试重新和P绑定,如果P满了则当前G会加入全局队列,如果当前M和P绑定失败则M会尝试和其他的P绑定,如果都没有当前M会进行睡眠状态

如果若干线程中一个线程OOM,会发生什么?如果是Goroutine 呢?项目中出现过OOM吗,怎么解决的?

项目中错误处理是怎么做的?

接口返回值定义errNo,data,errStr
如果有异常需要接口抛错误的话直接return
if err != nil {
  return c.String{"errNo":xx,"errStr":"xx","data":{}}
}

如果若干个Goroutine,其中有一个panic,会发生什么?

单独一个goroutine panic不会影响到其他goroutine的执行,panic不会影响defer,所以在主程序中用recover函数来捕获panic即可

defer可以捕获到其Goroutine的子Goroutine 的panic吗?

defer不可以捕捉其子goroutine的panic

开发用Gin框架吗?Gin怎么做参数校验?

 ShouldBind通过标签来判断参数是不是必须的

中间件使用过吗?怎么使用的。Gin的错误处理使用过吗?Gin中自定义校验规则知道怎么做吗?自定义校验器的返回值呢?

middleware目前只用了recover的捕获,在main函数启动的时候加载到appEngine中

golang中解析tag是怎么实现的?反射原理是什么?通过反射调用函数

通过reflect 的typeof和valueof来判断类型以及获取值的

golang的锁机制了解过吗? Mutex的锁有哪几种模式,分别介绍一下? Mutex锁底层如何实现了解过吗?

channel、channel使用中需要注意的地方?

数据库用的什么?数据库锁有了解吗?mysql锁机制讲一下。mysql分库分表。

mysql, 锁主要读锁 写锁
读锁:在读的时候其他人不能写
写锁:在写的时候其他人不能读不能读写

分库分表:一般都是一个业务或者一类业务功能单独使用1个库,分表一般来说都是按照章节 课程 一些业务属性来分表

讲一下redis分布式锁?redis主从模式和集群模式的区别了解过吗?redis的数据类型有哪些?redis持久化怎么做的?

setnx,如果当前是锁状态,获取时会返回false
主从模式 一般由三个节点当作一个实例 一主二从,如果主节点挂掉,那么此时从节点会当作主节点,主节点变为从节点
集群模式 相当于多个多个实例组成1个集群,有proxy根据key的hash值来判断当前key是属于那个实例的,如果单实例主节点挂掉,哨兵会组织其他实例选举当前挂掉实例的主节点
持久化:rdb快照方式 aof单条写
一般采用混合写,aof里面存储快照产生的二进制数据

数据类型包含 set,list,string,zset

编程题:你了解的负载均衡算法有什么?实现一个负载均衡算法。