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语言中的 new 和 make 主要区别如下:
- make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据;
- new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
- new 分配的空间被清零。make 分配空间后,会进行初始化;
了解过golang的内存管理吗?
了解过,首先说一下虚拟内存
虚拟内存是通过页和页表来存储的,大致如下图,程序一般不会直接操作内存而是通过操作虚拟内存来拿到数据
每页中都是以数组的方式进行存储的,包含有效位(是否被缓存在内存中或者cpu中)和物理磁盘位置以及下标
局部性原理:可能会被问到,由于内存空间都是连续的,在读写操作时,比如for循环写入到变量中,如果数组的下表不是连续的则效率会变低
当前golang语言由于内置内存管理,所以不需要自己去实现内存管理如果需要实现内存管理可以通过cGo这个包来操作C语言来进行内存管理
golang内存采用了TCMalloc(TC麦老客 读法)算法
大概需要掌握几个基本的概念
golang调度器 简称P
golang内核线程 简称M
golang协程 简称G
每个调度器都会和一个内核线程做绑定(PMG算法)通过P来调度G去M中执行 ,PMG算法执行流程如下图
那么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,而Mcache和MCentral交换的单位为span,Mcentral和Hpage交换的单位是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
小对象申请内存图
大对象申请内存图
调用函数传入结构体时,应该传值还是指针﹖说出你的理由?
传值会减少内存逃逸,减少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