golang基础面试题

Posted 福大大架构师每日一题

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang基础面试题相关的知识,希望对你有一定的参考价值。

启动流程

Q.go的init函数是什么时候执行的?

Q.多个init函数执行顺序能保证吗?

Q.go init 的执行顺序,注意是不按导入规则的(这里是编译时按文件名的顺序执行的)

Q.init函数能被外部调用吗?

内存分配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p22eNrMY-1646530923570)(C:\\Users\\MSI\\AppData\\Local\\Temp\\1646493064752.png)]

代已未堆栈环命

go内存结构:三个区域:span区512MB(注意:mspan在arena区),bitmap区16GB,arena区512GB

内存管理组件:mcache无锁分配, mcentral, mheap

mspan是双向链表

tiny分配器,减少内存浪费率,但回收困难,所有对象可回收才能回收

span至少1个page(8k),被划分成固定大小的slot,bitmap表示slot是否在使用

mchache无锁分配 ,mcentral,mheap

.>32kb,从mheap从获取

.<16且无指针,使用tiny分配器

.<16有指针或者16-32kb,从mchache中获取mspan中的slot

new

new和make有什么区别?

newobject函数

make

makeslice,makechan,makemap,makemap_small

Q.go对象在内存中是怎样的

Q.go的内存分配是怎么样的

Q.栈的内存是怎么分配的

Q.检测Golang内存泄漏的工具

A.pprof,trace,race检测

Q.go struct能不能比较?

A.字段类型和名称顺序一致,就可以做比较。跟结构体名称无关。

Q.Golang 里的逃逸分析是什么?怎么避免内存逃逸?

Q.简单介绍一下go的内存分配机制? 有mcentral为啥要mcache?
A.答了 mcentral是服务所有系统线程,mcache为系统线程独享,mcache缺少span时去mcentral->mheap 中取

Q.内存对齐

A.跟c++的一样,8字节对齐

Q.go 内存分配,和 tcmalloc 的区别

A.借用了tcmalloc的思想

Q.内存分配的不同方法的优缺点(固定分区、动态分区、页式分配)

A.

Q.copy是操作符还是内置函数

A.内置函数

Q.Go怎么做深拷贝

A.先序列化,再反序列化

Q.了解内存泄漏吗?有什么危害?

A.占资源,甚至程序奔溃

Q.空结构体的用处

A.1.map。

value是空结构体,构造集合。

2.通道。

只传递信号,不传递数据。

3.切片。

不管切片多长,都不会占用空间。

4.仅包含方法的结构体。

不用指针,节约空间。

5.最后零字段。

final zero field:结构体里的最后一个属性如果是空结构体,会当成1个字节处理。如果结构体嵌套的全是空结构体,还是0个字节。

Q.golang如何确定有没有内存泄露?系统里怎么去监控整体的运行情况?日志是怎么处理的?

A.

Q.虚拟内存地址

Q.char *Ptr=0; *Ptr=‘a’ 说明一下内存分配流程

A.报错。

Q.什么是内存逃逸,在什么情况下发生,原理是什么?

A.方法内变量是值,返回了变量地址,这个时候会出现内存逃逸

Q.逃逸分析说下?为什么要逃逸分析?如何避免逃逸

A.少用指针

Q.连接池的好处

A.复用,减少资源消耗。

Q.byte和rune有什么区别

A.rune是int32,占用的字节数不一样

Q.malloc怎么减少内存碎片 (?啥玩意不会)

A.极小分配器,会内存合并

Q.Go语言内存分配,什么分配在堆上,什么分配在栈上。【我顺便提了一下内存逃逸,应该算加分了嘻嘻】

A.局部变量,入参,全局变量存在栈上。引用类型存堆上。

Q.数据在内存中的存储形式

A.

Q.能说说栈在实际中的应用吗

A.

Q.用什么编译器

A.

Q.go的启动过程

A.

Q.go怎么实现封装继承多态

A.用struct模拟类。

用组合实现继承。

用接口实现多态。

Q. 知道浮点数在机器上面怎么存储的

A.符号位,阶码,尾数

Q.go语言的时候垃圾回收,写代码的时候如何减少小对象分配

A.

Q.简单介绍一下go的内存分配机制? 有mcentral为啥要mcache?
答了mcentral是服务所有系统线程,mcache为系统线程独享,mcache缺少span时去mcentral->mheap中取

Q.当写一个程序申请内存时,会做哪些操作?

A.中断,页面置换,堆,栈等。

Q.什么方法可以限制从堆分配内存的内存范围?

A.

Q.说一下 string 和 []byte 的高效转换

Q.Go的数据结构的零值是什么

A.引用类型是nil,其他是0,0.0,""等等

垃圾回收

夜读53:27

刘丹兵垃圾回收

1.3 版本是标记清除算法,stw时间过长

STW,stop the world;让程序暂停,程序出现卡顿 (重要问题)。
标记需要扫描整个heap
清除数据会产生heap碎片

gcStart

三色标记法,“强-弱” 三色不变式、插入屏障、删除屏障、混合写屏障、STW

有两个问题, 在三色标记法中,是不希望被发生的

条件1: 一个白色对象被黑色对象引用(白色被挂在黑色下)
条件2: 灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)
当以上两个条件同时满足时, 就会出现对象丢失现象!

插入写屏障:A引用C,A黑C白,会把C加入写屏障buf,最终flush到扫描队列,stw

不足:结束时需要stw重新扫描栈,大约需要10~100ms

删除屏障:具体操作: 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。回收精度低。
满足: 弱三色不变式. (保护灰色对象到白色对象的路径不会断)

插入写屏障和删除写屏障的短板:
插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;
删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

混合写屏障:前提条件栈引用的对象都是黑色,后来添加的依然是黑色。

span里有gcmarkBits,0白色,1灰色或黑色

三色标记:

通过mspan查看是否被引用

灰色:对象已被标记,但这个对象包含的子对象未标记
黑色:对象已被标记,且这个对象包含的子对象也已标记,gcmarkBits对应的位为1(该对象不会在本次GC中被清理)
白色:对象未被标记,gcmarkBits对应的位为0(该对象将会在本次GC中被清理)
例如,当前内存中有A~F一共6个对象,根对象a,b本身为栈上分配的局部变量,根对象a、b分别引用了对象A、B, 而B对象又引用了对象D,则GC开始前各对象的状态如下图所示:

初始状态下所有对象都是白色的。
接着开始扫描根对象a、b; 由于根对象引用了对象A、B,那么A、B变为灰色对象,接下来就开始分析灰色对象,分析A时,A没有引用其他对象很快就转入黑色,B引用了D,则B转入黑色的同时还需要将D转为灰色,进行接下来的分析。
灰色对象只有D,由于D没有引用其他对象,所以D转入黑色。标记过程结束
最终,黑色的对象会被保留下来,白色对象会被回收掉。

root对象:栈,全局对象等

Q.gc触发时机

A.分配内存时,达到GOGC比例

后台触发-sysmon检测

手动触发-runtime.GC()

Q.Golang 的 GC 触发时机是什么

A.阈值触发;主动触发;两分钟定时触发;

Q.执行时为啥需要STW?

A.漏标,多标

Q.如何解决漏标?

A.满足三色不变性

Q.如何满足三色不变性?

A.屏障技术。

Q.哪里记录了对象的三色标记状态?

A.

Q.标记时,拿到一个指针,怎么知道它是哪个对象?也许是某个对象的内部指针?这个对象的内存哪些地方代表了它引用的对象呢?

A.

Q.gc怎么帮我们回收对象

A.三色标记

Q.go的gc会不会漏掉对象或者回收还在用的对象?

A.不会回收还在用的对象,如果有这个问题,早就歇菜了。

当前gc漏掉是有,但在下一次gc的时候就不会漏掉。

Q.gc会不会太慢,跟不上内存分配的速度?

Q.gc会不会暂停我们的应用?暂停多久?影不影响我的请求?

A.STW会暂停,肯定有影响。不过最新的垃圾回收机制将STW时间降低到了极致。

Q.如果a=5, b=a , c = &a ,gc启动后,a,b,c是什么颜色的?为什么?

A.

Q.go的GC和Python的GC

A.go是可达性分析,Python是引用标记。

Q.go语言的时候垃圾回收,写代码的时候如何减少小对象分配

A.结构体内部字段,考虑内存对齐。其他情况不清楚。

Q.string和byte数组有什么区别?

A.byte数组可修改,string不可修改。底层是一样的。

Q.STW是哪些阶段发生的?rescan-stack是为了什么?了解新版本go对写屏障的改进吗?

A.扫描栈会STW。新版本是混合写屏障,不需要重新扫描栈。

Q.两次GC周期重叠会引发什么问题,GC触发机制是什么样的?

Q.那如果用户在并发CMS期间改了引用,写屏障如何保证三色不变性?

A.插入屏障和删除屏障共同保证

Q.pprof使用

A.

调度器

newproc

gmp 23:18

刘丹冰09:18

03:12 trace代码

runnext 123→312

g排队逻辑,g调度逻辑调度顺序

GODEBUG=schedtrace=1000 可执行程序

M比P多,不会多太多,最大1万个

每个P都有本地运行队列,满了放全局运行队列

本地运行队列,不超过256个,全局运行队列

GM分离,网络或者锁切换

PM分离,系统调用阻塞或cgo

Q.runtime包里面的方法

Q.go的调度为什么说是轻量的

A.跟线程比,是轻量了

Q.go调度都发生了啥

A.gmp

Q.go网络和锁会不会阻塞线程

A.会。

Q.什么时候阻塞线程

A.休眠,加锁等等。

Q.协程同步的方式 waitgroup和context区别

A.waitgroup收集次数,context不收集次数

Q.什么场景不适合用协程?那该用什么实现并发呢?(epoll)

A.

Q.创建多个goroutine在多核的情况下是如何分配的

A.gmp

Q.一个main函数内用go 开启多个协程,现在一个协程panic了,main函数会怎样? 为什么?
A.整个程序就奔溃了

Q.Golang 的协程间通讯方式有哪些?
共享内存和协程通信。

Q.说一下go协程设计

A.由于自己用c实现过协程,所以答的很随意。讲了一下函数调用约定,栈布局,上下文切换,x86寄存器,又讲了一下用gcc的"-finstrument-functions"。然后讲了一下go的调度思路。

参考go源码runtime.schedule

Q.go实现协程池

A.基本上就是用channel实现。

Q.golang gmp模型怎么调度的,一个goroutine sleep了,操作系统是怎么唤醒的

A.信号量、互斥锁、条件变量

Q.Go如何调度,假设4核的cpu应该有几个线程或者说有几个M,那能有几个groutinue,groutinue数量的上限是多少?

A.理论无上限

Q.groutinue什么时候会被挂起

A.非io

Q.主线程控制协程的方法

A.

Q.goroutine泄露,或是内存泄漏。

A.

Q.go的goroutine ,如何停止一个goroutine?

A.(我觉得面试官的意思是如何控制goroutine的退出,可以用channel和Context)

Q.起几个线程死循环 cpu会爆吗?

A.会。四核八线程,8个协程就能让cpu100%

Q.如何实现只开100个协程

A.用channel控制,容量是100

Q.golang如何知道或者检测死锁

A.pprof

Q.golang怎么操作内核线程

A.

Q.生产中哪些服务用的 进程、线程,为什么要这么做,有什么好处

A.

Q.什么情况下 M 会进入自旋的状态?

A.(M 是系统线程。为了保证自己不被释放,所以自旋。这样一旦有 G 需要处理,M 可以直接使用,不需要再创建。M 自旋表示此时没有 G 需要处理)

Q.Golang 怎么在并发编程中等待多个 goroutine 结束?

A.channel,sync.WaitGroup

Q.怎么限制goroutine的数量?

A.channel

Q.golang调度 能不能不要p

A.可以不要,但不好控制。

Q.协程泄***r>如果你启动了一个 goroutine,但并没有符合预期的退出,直到程序结束,此goroutine才退出,这种情况就是 goroutine 泄露。当 goroutine 泄露发生时,该 goroutine 的栈(一般 2k 内存空间起)一直被占用不能释放,goroutine 里的函数在堆上申请的空间也不能被 垃圾回收器 回收。

A.

Q.协程中参数直接使用,和传参的区别是什么?为什么会造成这种结果。

A.直接使用是闭包,传参跟普通函数一样了。

Q.为什么P的local queue可无锁访问,任务窃取的时候要加锁吗?

A.用cas

Q.看你项目里面自己造了个go协程池,怎么实现的?协程池上限确定的原则是什么?

A.

Q.知道go的伪抢占式调度吗?新版本?举个goroutine泄漏的例子?

A.

Q.goroutine和线程的区别:

A.goroutine 是建立在线程之上的轻量级的抽象,允许以非常低的代价在同一地址空间中并行地执行多个函数和方法。相比如线程,创建和销毁的代价更小,调度是独立于线程之外的。

Q.为什么说goroutine轻量:

A.1 创建Goroutine 通常只需要2kb 的内存,但是线程则需要1mb 2 . go 中创建和销毁都是自己管理的,而不是像操作系统申请资源,销毁再归还。3 GMP调度

Q.协程内部再启用协程,它们是没关系,对吧?外面的协程奔溃,里面的还会执行吗?外部协程执行结束的时候,如何让内部协程也停止运行?golang原生提供的包里,让内部协程停止运行。

A.没法直接停止,用context打配合。

Q.协程直接如何通信?

A.共享内存和通道

Q.进程间的通信方式

A.

Q.使用共享内存的通信方式如何加锁

A.

Q.进程、线程、Goroutine的比较 ,各自结构,内存分配

A.

Q.线程什么是私有的

A.

Q.线程的局部变量被访问会造成什么

A.

Q.如果要在每个goroutine中都获取返回值(捕获参数),有哪些方案?

A.(全局参数、channel,闭包)

Q.gmp当一个g堵塞时,g、m、p会发生什么

A.cpu密集型:p和g分离,

io密集型:p和mg分离

Q.线程对比进程的优势只有寄存器少吗

A.

Q.Go创建协程的过程知道吗

A.

Q.线程池是核心态还是用户态创建的?

A.

Q.线程池里面的线程是什么状态?

A.

Q.线程之间哪些是共享的?线程切换需要切换上下文吗?切换哪些东西?

A.

Q.线程的切换是在核心态吗?

A.

Q.GPM模型里如果本地队列满了,新增的g会怎么处理

A.放全局队列

Q.go调度中阻塞都有那些方式

A.通道,睡眠,

Q.父goroutine退出,如何使得子goroutine也退出?

A.context

Q.一颗CPU,两个协程,其中一个协程在死循环,会发生什么

A.p和g分离,g放在全局队列

Q.用Channel和两个协程实现数组相加

A.

Q.用协程实现顺序打印123

A>

Q为什么不要频繁创建和停止goroutine

A.

Q.用go实现一个协程池,大概用什么实现

A.

Q.为什么不要大量使用goroutine

A.

Q. go里面goroutine创建数量有限制吗?

A.

Q.golang支持哪些并发机制

A.

Q.go用共享内存的方式实现并发如何保证安全?

A.

Q.服务能开多少个m由什么决定

A.

Q.开多少个p由什么决定

A.

Q.m和p是什么样的关系

A.

Q.同时启了一万个g,如何调度的?

A.

Q.如何拿到多个goroutine的返回值,如何区别他们

A.

Q.一个进程能创建的线程数量受到哪些制约?

A.

Q.goroutine创建数量有限制吗?

A.

Q.go语言的GMP模型,全局队列中的G会不会饥饿,为什么?P的数量是多少?能修改吗?M的数量是多少?

A.

Q.P和M的数量一定是1:1吗?如果一个G阻塞了会怎么样?

A.

Q.并发控制的方法?chan、sync包

A.

Q.1000个写请求100读请求,剩余900个写协程阻塞了,如何设计避免阻塞?

A.

Q.给你10k个任务,其中1%低优先级任务,其他为高优先级任务,双核CPU如何调度最为高效。

A.

Q.协程与线程区别?为什么快,快在哪了?

A.自定义了4个伪寄存器 fp sp sb pc,轻量,初始2KB动态伸缩,由runtime管理,对os透明等等各种原因,又说了以下gmp调度的优势。追问寄存器是什么,你对寄存器还有什么了解。我说了一点关于go和c 在寄存器方面使用的不同,函数传参,调用堆栈等等,扯了点汇编区别,就又问了几个汇编指令,过了

Q.GMP模型、说说go并发GMP版本迭代的过程?说说如何避免全局队列饥饿?

A.

问:stack里面保存什么?
答:method栈帧,pc,局部变量。
问:局部变量是什么?应该不会保存在stack里面吧?
答:java分配对象内存是在堆里面的,你可以理解为一个指针。
问:协程怎么停顿?
答:抛异常造成suspend,保存现场。
问:协程为什么高效?
答:避免内核中线程的上下文切换、协程数据更轻量。
问:还有吗?等了一会我没思路,然后面试官自己说了其实还有一个,线程是可以随时发生上下文切换的,而协程是需要在固定位置显式切换的,所以保存上下文更轻量。

Q.什么时候用多线程,什么时候用多进程

A.

Q.IO 密集型和 CPU 密集型如何分配线程优先级

A.

Q. go语言的并发问题,面试官输入了一段代码:

func main()

for i := 0; i < 10; i++

go fmt.Println(i)

A.主协程睡眠的情况下是乱序。一定会输出0-9的数字。原因在于i传参了。

不睡眠就没输出。

Q.Go的协程可以不可以自己让出cpu

A.runtime.Gosched()

Q.Go的协程可以只挂在一个线程上面吗

A.不能。可以保证一个P。runtime.GOMAXPROCS(1)

Q.一个协程挂起换入另外一个协程是什么过程?

A.

Q. Go里面一个协程能保证绑定在一个内核线程上面的。

A.

Q.如何让n个线程执行完后一起结束

A.sync.WaitGroup

channel

Q.在 IO 密集型场景下,可以适当调高 P 的数量

A.在 IO 密集型场景下,可以适当调高 P 的数量

通道

底层结构

创建

发送

接收

关闭

chansend1,chanrecv1

对一个关闭的通道再发送值就会导致panic。
对一个关闭的通道进行接收会一直获取值直到通道为空。
对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
关闭一个已经关闭的通道会导致panic。

空读写阻塞,写关闭异常,读关闭空零

操作nil channelclosed channelnot nil, not closed channel
closepanicpanic正常关闭
读 <- ch阻塞读到对应类型的零值阻塞或正常读取数据。缓冲型 channel 为空或非缓冲型 channel 没有等待发送者时会阻塞
写 ch <-阻塞panic阻塞或正常写入数据。非缓冲型 channel 没有等待接收者或缓冲型 channel buf 满时会被阻塞

数据交流:当作并发的 buffer 或者 queue,解决生产者 - 消费者问题。多个
goroutine 可以并发当作生产者(Producer)和消费者(Consumer)。
数据传递:一个 goroutine 将数据交给另一个 goroutine,相当于把数据的拥有权 (引
用) 托付出去。
信号通知:一个 goroutine 可以将信号 (closing、closed、data ready 等) 传递给另一
个或者另一组 goroutine 。
任务编排:可以让一组 goroutine 按照一定的顺序并发或者串行的执行,这就是编排的
功能。
锁:利用 Channel 也可以实现互斥锁的机制。

Q.优雅关闭channel?优雅关闭

A.如果sender(发送者)只是唯一的sender或者是channel最后一个活跃的sender,那么你应该在sender的goroutine关闭channel

不要从一个 receiver 侧关闭 channel,也不要在有多个 sender 时,关闭 channel。

sync.Once封装close方法

根据 sender 和 receiver 的个数,分下面几种情况:
一个 sender,一个 receiver
一个 sender, M 个 receiver
N 个 sender,一个 reciver
N 个 sender, M 个 receiver
对于 1,2,只有一个 sender 的情况就不用说了,直接从 sender 端关闭就好了,没有问题。重点关注第 3,4 种情况。

第3种情况,解决方案就是增加一个传递关闭信号的 channel,receiver 通过信号 channel 下达关闭数据 channel 指令。senders 监听到关闭信号后,停止发送数据。并不主动关闭。

Q.用go撸一个生产者消费型,用channel通信,怎么友好的关闭chan.
Q.有一道经典的使用 Channel 进行任务编排的题,你可以尝试做一下:有四个
goroutine,编号为 1、2、3、4。每秒钟会有一个 goroutine 打印出它自己的编号,
要求你编写一个程序,让输出的编号总是按照 1、2、3、4、1、2、3、4、……的顺序打
印出来。

Q.开俩个协程,一个协程生产数据,另一个协程对数据进行处理,处理完后再把数据发回去,使用管道如何实现?

Q.channel实现原理,为什么不用加锁?

Q.channel关闭以后,再往其发送或接收,会发生什么

Q.连续关闭两次管道会发生什么

Q.如何判断channel是否关闭?

Q.channel底层实现 :buf,sendx,recvx,lock, sendq , recvq ; hchan 结构体
并发安全:CSP channel 通信加锁

Q.Channel的阻塞和非阻塞(顺带问了select用法)

Q.channel了解吗,channel的工作原理是什么?

Q.golang的channel,对已经关闭的channel进行读写操作会发生什么

Q.channel的实现原理?答了环形队列,被追问为什么用环形队列

Q.写个channel相关的题,并发模型,爬虫url,控制并发量

Q.关闭channel有什么作用?

Q.被close的channel会有什么问题

Q.分布式锁知道哪些?用channel如何实现?

Q.怎么理解“不要用共享内存来通信,而是用通信来共享内存”

Q.channel和共享内存有什么优劣势?

Q.channel和锁对比一下

Q.向为nil的channel发送数据会怎么样

Q.关闭一个已关闭的chan会如何

Q.有生产者和消费者应该在哪里关闭通道?

Q.继续追问多个生产者的情况,在哪关闭通道?(答sync.waitgroup来计数,再开启一个协程wg.wait()监听所有生产者。面试官说不是他想要的。是想要atomic来计数吗?求大佬解惑。面试超时反问时没时间问了。。)

Q.手写生产者消费者模型

Q.写多生产者多消费者模型

延迟

Q.如何处理异常 defer

Q.假设在一个函数体中对临界资源进行加锁和解锁,使用defer进行解锁和自己手动解锁有什么区别?

Q.defer函数的使用场景(延迟Close、recover panic)

Q.defer的执行顺序?

Q.go 语言的 panic 如何恢复

Q.defer关键字后的函数在什么时候调用 主函数return前还是return后: defer的执行顺序在return之后,但是在返回值返回给调用方之前,所以使用defer可以达到修改返回值的目的。

Q.defer A ; defer B ; defer panic("") A和B能不能执行到

Q.defer的执行流程

Q.怎么保存在程序崩溃时的数据,当时没理解到,我觉得是(defer+reciver)

切片

切片结构

type slice struct
array unsafe.Pointer//指针
len int//长度
cap int//容量

// An notInHeapSlice is a slice backed by go:notinheap memory.
type notInHeapSlice struct
array *notInHeap//指针
len int//长度
cap int//容量

//nil切片可以append
//※创建切片func makeslice(et *_type, len, cap int) unsafe.Pointer
// 所需要分配的内存大小
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
// 判断根据容量内存大小是否超过限制 数组长度和容量是否合法
if overflow || mem > maxAlloc || len < 0 || len > cap
// NOTE: Produce a ‘len out of range’ error instead of a
// ‘cap out of range’ error when someone does make([]T, bignumber).
// ‘cap out of range’ is true too, but since the cap is only being
// supplied implicitly, saying len is clearer.
// See golang.org/issue/4085.
mem, overflow := math.MulUintptr(et.size, uintptr(len))
// 判断根据长度内存大小是否超过限制、数组长度是否合法
if overflow || mem > maxAlloc || len < 0
panicmakeslicelen()

panicmakeslicecap()

return mallocgc(mem, et, true)

扩容策略

如果2倍容量还小于新增的容量,那就取新增的容量
如果新容量大于原来容量的二倍直接用新容量。
如果原来切片的容量小于1024,于是扩容的时候就翻倍增加容量。
一旦元素个数超过 1024,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。

//扩容

// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice’s length is set to the old slice’s length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice’s length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
func growslice(et *_type, old slice, cap int) slice

Q.go的 slice扩容机制,slice 在for一遍会改变内容吗

Q.Slice是否线程安全

Q.讲下golang的slice和string底层,空slice和nil的slice区别?能直接append吗?扩容?

Q.Golang slice 不断 append,是如何给它分配内存的?slice 如果分配的 capacity 是 10,那当容量到多大的时候才会扩容?8、9、10?

Q.切片和数组差别

Q.如何删除slice中间的元素(s = append(s[:i],s[i+1,]…),我感觉其实就是切片的应用。

string

Q.Go string底层实现?

Q.[]byte和string互转的高效方法

Q.那你这个用unsafe.Pointer和uintptr的方案,不会有问题吗?string少的那个cap字段怎么填充?(我答可能GC会有点影响,因为unsafe.Pointer指向的对象不会被GC回收了)

Q.string类型转为[]byte过程发生了什么

Q.slice作为函数参数怎么解决上面的问题?

Q.数组相比slice的优点在哪里

A.可比较

编译安全

长度是类型

规划内存布局

访问速度

map

https://golang.design/go-questions/map/principal/
key 类型的 K 必须是可比较的(comparable),也就是可以通过 == 和 != 操作符
进行比较;value 的值和类型无所谓,可以是任意的类型,或者为 nil。
在 Go 语言中,bool、整数、浮点数、复数、字符串、指针、Channel、接口都是可比较
的,包含可比较元素的 struct 和数组,这俩也是可比较的,而 slice、map、函数值都是不
可比较的。

使用 map 的 2 种常见错误
常见错误一:未初始化
常见错误二:并发读写

Q.hash冲突的解决办法有哪些?

Q.如果一个map没申请空间,去向里面取值,会发生什么情况。我记得好像是返回默认值,面试官问我确定吗…

Q.如何实现一个每次遍历顺序都一样的map

Q.项目里的map并发怎么做? 为啥用分段锁不用sync.map? 分段锁拆了几个分片?

Q.hashmap数据太多 rehash时间长 怎么解决

Q.rehash时候可以get put吗

Q.golang中两个map对象如何比较

Q.哈希的实现有哪几种,如何取hashcode,冲突检测几种方法

Q.map遍历的时候每次顺序都是固定的吗?为什么?

Q.Map可以用数组作为Key吗(数组可以,切片不可以)

Q.如何实现Map的有序查找(利用一个辅助slice)

Q.怎么实现Map的并发安全(sync.Map,底层实际上用了一个Map缓存)

Q.扩容机制?

Q.sync.map与map的区别,怎么实现的【答了个大概,这个没答好】

Q.map哈希过程(讲错了一点点,忘了可能插入相同键不同值)

Q.哈希过程是什么样子的

Q.桶的增加(这个具体还挺复杂的)

Q.map中对于key的哈希是怎么计算的?

Q.map取一个key,然后修改这个值,原map数据的值会不会变化

Q.Hash实现、冲突解决、应用

Q.map里面解决hash冲突怎么做的,冲突了元素放在头还是尾

Q.项目里的map并发怎么做? 为啥用分段锁不用sync.map?分段锁拆了几个分片?

Q. hash冲突解决办法,有什么弊端

Q. map里面解决hash冲突怎么做的,冲突了元素放在头还是尾

接口

接口不能直接使用接收者为值类型的方法。

Q.go里面interface是什么概念

Q.那你说下interface底层实现

Q.go语言中结构体指针为空,赋给一个interface为什么interface不为空

Q.interface 不是个好的形式,会导致 GC 压力大,为啥,那用什么形式比较好

反射

Q.golang类型断言,怎么用

Q.类型断言用过吗,说说实现,如何判断断言成功?

Q.反射是什么

select

Q.golang的多路复用

A.select channel

Q.Go语言的Select 与 I/O多路复用的Select区别

Q.select的用法,加上default又会怎么样?

context

上下文信息传递
控制子goroutine得运行
超时控制的方法调用
可以取消的方法调用
Q.使用 WithCancel 和 WithValue 写一个级联的使用 Context 的例子,验证一下 parent
Context 被 cancel 后,子 conext 是否也立刻被 cancel 了。

Q.问了context的作用,哪些场景使用过context?

Q.context包内部如何实现的?

Q.go 怎么控制查询timeout (context)

Q.context包的用途?

Q.Context 包的作用
编译时做接口检查(PS: E.g. var _ InterfaceName := (*TypeName)(nil))
运行时做接口检查(PS: E.g. _, ok := TypeName.(InterfaceName))

容器

heap

list

ring

Q.用go构造一个链表怎么做,想要从链表尾部插入,怎么做(我听到这个问题懵了一下?然后就基于ListNode和List结构体,说了一下,然后在List结构体里保存头尾指针这样)
B站app的页面分区怎么设计(这个一开始没想到应该怎么回答,最后回答的是数据库表的设计,主键和外键)

同步库

条件变量Cond

Q.一个 Cond 的 waiter 被唤醒的时候,为什么需要再检查等待条件,而不是唤醒后进行
下一步?
Q.你能否利用 Cond 实现一个容量有限的 queue?

映射

Q.为什么 sync.Map 中的集合核心方法的实现中,如果 read 中项目不存在,加锁后还要
双检查,再检查一次 read?
Q.你看到 sync.map 元素删除的时候只是把它的值设置为 nil,那么什么时候这个 key 才
会真正从 map 对象中删除?

go run -race counter.go

开启了 race 的程序部署在线上,还是比较影响性能的。运行 go tool compile -
race -S counter.go,可以查看计数器例子的代码,重点关注一下 count++ 前后的编译后
的代码

在编译的代码中,增加了 runtime.racefuncenter、runtime.raceread、
runtime.racewrite、runtime.racefuncexit 等检测 data race 的方法。通过这些插入的指
令,Go race detector 工具就能够成功地检测出 data race 问题了

互斥量

go并发编程实战课

type Mutex struct 
    state int32
    sema uint32

const (
    mutexLocked = 1 << iota // 持有锁的标记
    mutexWoken // 唤醒标记
    mutexStarving // 从state字段中分出一个饥饿标记
    mutexWaiterShift // 阻塞等待的waiter数量

    starvationThresholdNs = 1e6


Q.目前 Mutex 的 state 字段有几个意义,这几个意义分别是由哪些字段表示的?
A.

可重入锁,try锁(通过 unsafe 的方式读取到 Mutex 内部的 state
字段,),需要手动实现
Q.等待一个 Mutex 的 goroutine 数最大是多少?是否能满足现实的需求?
Q.常见的 4 种错误场景
A.1.Lock/Unlock 不是成对出现
2.Copy 已使用的 Mutex 。 go vet 检查这个 Go 文件
3.重入
4.死锁
过 Go 运行时自带的死锁检测工具,或者
是第三方的工具(比如go-deadlock、go-tools)进行检查

Go pprof 工具分析,它提供了一个 block profiler 监控阻塞的

Q.如何实现可重入锁?
方案一:通过 hacker 的方式获取到 goroutine id,记录下获取锁的 goroutine id,它
可以实现 Locker 接口。
runtime.Stack 方法获取栈帧信息,栈帧信息里包含 goroutine id。
petermattis/goid也可以获取goid
方案二:调用 Lock/Unlock 方法时,由 goroutine 提供一个 token,用来标识它自
己,而不是我们通过 hacker 的方式获取到 goroutine id,但是,这样一来,就不满足
Locker 接口了。

//go get -u github.com/petermattis/goid 
// RecursiveMutex 包装一个Mutex,实现可重入
type RecursiveMutex struct 
	sync.Mutex
	owner     int64 // 当前持有锁的goroutine id
	recursion int32 // 这个goroutine 重入的次数


func (m *RecursiveMutex) Lock() 
	gid := goid.Get()
	// 如果当前持有锁的goroutine就是这次调用的goroutine,说明是重入
	if atomic.LoadInt64(&m.owner) == gid 
		m.recursion++
		return
	
	m.Mutex.Lock()
	// 获得锁的goroutine第一次调用,记录下它的goroutine id,调用次数加1
	atomic.StoreInt64(&m.owner, gid)
	m.recursion = 1

func (m *RecursiveMutex) Unlock() 
	gid := goid.Get()
	// 非持有锁的goroutine尝试释放锁,错误的使用
	if atomic.LoadInt64(&m.owner) != gid 
		panic(fmt.Sprintf("wrong the owner(%d): %d!", m.owner, gid))
	
	// 调用次数减1
	m.recursion--
	if m.recursion != 0  // 如果这个goroutine还没有完全释放,则直接返回
		return
	
	// 此goroutine最后一次调用,需要释放锁
	atomic.StoreInt64(&m.owner, -1)
	m.Mutex.Unlock()

// Token方式的递归锁
type TokenRecursiveMutex struct 
	sync.Mutex
	token     int64
	recursion int32


// 请求锁,需要传入token
func (m *TokenRecursiveMutex) Lock(token int64) 
	if atomic.LoadInt64(&m.token) == token  //如果传入的token和持有锁的token一致,
		m.recursion++
		return
	
	m.Mutex.Lock() // 传入的token不一致,说明不是递归调用
	// 抢到锁之后记录这个token
	atomic.StoreInt64(&m.token, token)
	m.recursion = 1


// 释放锁
func (m *TokenRecursiveMutex) Unlock(token int64) 
	if atomic.LoadInt64(&m.token) != token  // 释放其它token持有的锁
		panic(fmt.Sprintf("wrong the owner(%d): %d!", m.token, token))
	
	m.recursion--         // 当前持有这个锁的token释放锁
	if m.recursion != 0  // 还没有回退到最初的递归调用
		return
	
	atomic.StoreInt64(&m.token, 0) // 没有递归调用了,释放锁
	m.Mutex.Unlock()

Q.你可以为 Mutex 获取锁时加上 Timeout 机制吗?会有什么问题吗?
Q.互斥锁的底层实现

Q.go中的互斥锁:正常、饥饿状态,读写锁中写操作如何阻止读操作?

Q.go的锁是可重入的吗?

Q.获取不到锁会一直等待吗?

Q.那如何实现一个timeout的锁?

Q.读写锁说下

Q.无锁编程

Q.写一个会产生死锁的代码

Q.使用共享内存的通信方式如何加锁

Q.sync.Map 的使用,与自己写的阻塞锁有什么区别?那个更快

Q. golang中的CAS问题

Q.sync包了解吗

Q.sync pool的实现原理

Q.golang中的CAS问题

读写锁

readers-writers 问题
Read-preferring:读优先的设计可以提供很高的并发性,但是,在竞争激烈的情况下
可能会导致写饥饿。这是因为,如果有大量的读,这种设计会导致只有所有的读都释放
了锁之后,写才可能获取到锁。
Write-preferring:写优先的设计意味着,如果已经有一个 writer 在等待请求锁的
话,它会阻止新来的请求锁的 reader 获取到锁,所以优先保障 writer。当然,如果有
一些 reader 已经请求了锁的话,新请求的 writer 也会等待已经存在的 reader 都释放
锁之后才能获取。所以,写优先级设计中的优先权是针对新来的请求而言的。这种设计
主要避免了 writer 的饥饿问题。
不指定优先级:这种设计比较简单,不区分 reader 和 writer 优先级,某些场景下这种
不指定优先级的设计反而更有效,因为第一类优先级会导致写饥饿,第二类优先级可能会导致读饥饿,这种不指定优先级的访问不再区分读写,大家都是同一个优先级,解决
了饥饿的问题。

Go 标准库中的 RWMutex 设计是 Write-preferring 方案。一个正在阻塞的 Lock 调用
会排除新的 reader 请求到锁。

type RWMutex struct 
	w Mutex // 互斥锁解决多个writer的竞争
	writerSem uint32 // writer信号量
	readerSem uint32 // reader信号量
	readerCount int32 // reader的数量
	readerWait int32 // writer等待完成的reader的数量

const rwmutexMaxReaders = 1 << 30

不可复制
重入导致死锁
释放未加锁的RWMutex

单例Once

// Once is an object that will perform exactly one action.
//
// A Once must not be copied after first use.
type Once struct 
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex

// 一个功能更加强大的Once
type Once struct 
	m sync.Mutex
	done uint32

// 传入的函数f有返回值error,如果初始化失败,需要返回失败的error
// Do方法会把这个error返回给调用者
func (o *Once) Do(f func() error) error 
	if atomic.LoadUint32(&o.done) == 1  //fast path
		return nil
	
	return o.slowDo(f)

// 如果还没有初始化
func (o *Once) slowDo(f func() error) error 
	o.m.Lock()
	defer o.m.Unlock()
	var err error
	if o.done == 0  // 双检查,还没有初始化
		err = f()
		if err == nil  // 初始化成功才将标记置为已初始化
			atomic.StoreUint32(&o.done, 1)
		
	
	return err

第一种错误:死锁.once。Do嵌套
第二种错误:未初始化
Q.我已经分析了几个并发原语的实现,你可能注意到总是有些 slowXXXX 的方法,从
XXXX 方法中单独抽取出来,你明白为什么要这么做吗,有什么好处?
Q.Once 在第一次使用之后,还能复制给其它变量使用吗?

bytebufferpool
oxtoacart/bpool

连接池
标准库中的 http client 池
TCP 连接池
数据库连接池
Memcached Client 连接池
Worker Pool

等待组WaitGroup,相当于java中的CountDownLatch

Add,用来设置 WaitGroup 的计数值;
Done,用来将 WaitGroup 的计数值减 1,其实就是调用了 Add(-1);
Wait,调用这个方法的 goroutine 会一直阻塞,直到 WaitGroup 的计数值变为 0。

type WaitGroup struct 
	// 避免复制使用的一个技巧,可以告诉vet工具违反了复制使用的规则
	noCopy noCopy
	// 64bit(8bytes)的值分成两段,高32bit是计数值,低32bit是waiter的计数
	// 另外32bit是用作信号量的
	// 因为64bit值的原子操作需要64bit对齐,但是32bit编译器不支持,所以数组中的元素在不同的
	// 总之,会找到对齐的那64bit作为state,其余的32bit做信号量
	state1 [3]uint32


常见问题一:计数器设置为负值
第一种方法是:调用 Add 的时候传递一个负数
第二个方法是:调用 Done 方法的次数过多,超过了 WaitGroup 的计数值。
常见问题二:不期望的 Add 时机
常见问题三:前一个 Wait 还没结束就重用 WaitGroup

noCopy:辅助 vet 检查
Q.通常我们可以把 WaitGroup 的计数值,理解为等待要完成的 waiter 的数量。你可以试着
扩展下 WaitGroup,来查询 WaitGroup 的当前的计数值吗?

原子库

timer

time/tick.go

time/sleep.go

Time和runtimeTimer

tick多了period

Q.while(tree)sleep(1)这个会有什么问题

Q.sleep底层实现原理

设计模式

Q.介绍除了单例与工厂模式外的设计模式(消费者模式)

grpc

Q.grpc用的什么协议?

Q.流式rpc是怎么处理的?

Q.RPC是基于TCP和UDP?数据传输过程中的简单流程又是怎样的?

框架

Q.看我有使用Kratos框架,问这个框架有哪些特性?

Q.Gin框架如何保证并发请求消息准确不出错?

Q.说到了protob

以上是关于golang基础面试题的主要内容,如果未能解决你的问题,请参考以下文章

golang笔记-面试题整理01

golang笔记-面试题整理01

剖析Linux内核100道面试题及内存屏障

面向校招Golang面试题总结

面向校招Golang面试题合集

面向校招Golang面试题总结