参与过口试的企业有:zg人寿,睿科lun,qi猫,yun汉商城,zi节跳动,特斯la,虾P,chuan音,qi安信,ai立信等大大小小企业近30家,BAT简历都过不了。
如果末了对你有帮助,帮忙点个关注!
技能部分

1)、算法部分,刷LeetCode就完事了,这是一个长期的过程,短期突击没啥效果,由于题目太多了。
2)、措辞根本,细分为:golang的根本及事理,便是本文紧张内容了;mysql根本及事理;redis根本及事理;kafka或其他中间件(如果没用过,须要理解大概的底层事理及构造);linux常用的命令,比如定时脚本几个参数韶光分别代表啥,文件权限须要搞清楚,进程内存占用命令;小公司还要懂一些前真个知识,由于他们希望你什么都会。
3)、项目履历,可以搞一个基于gin的后端接口做事的web框架,一样平常会问你怎么实现的;以及微做事理解一下。
非技能部分
1)由于上海5月份居家办公,远程口试,这些题目准备一份,碰着卡壳的题目完备可以参考你准备好的答案,由于视频口试你眼睛是看着口试官还是题目是不太随意马虎区分的(把题目窗口置顶)。
2)HR面也可以完备准备一份可能问到的问题的答案,并不是说你不会回答,而是会让你的表达更顺畅,其次也解释你是有备而来的,我在某拉公司口试就吃了这个亏,技能通过,HR说我的表达能力弗成(后续我也会把这个模板分享出来,感谢我媳妇充当口试官,以及辅导如何高情商的回答HR的问题)。
3)可以自己录音口试回答,看看自己的语气、音量,顺畅度,如果自己听了都不舒畅,口试官可能也不舒畅。
一、根本部分1、golang 中 make 和 new 的差异?(基本必问)共同点:给变量分配内存
不同点:
1)浸染变量类型不同,new给string, int和数组分配内存,make给切片,map,channel分配内存;
2)返回类型不一样,new返回指向变量的指针,make返回变量本身;
3)new 分配的空间被清零。make 分配空间后,会进行初始化;
4) 字节的口试官还说了其余一个差异,便是分配的位置,在堆上还是在栈上?这块我比较模糊,大家可以自己探究下,我搜索出来的答案是golang会弱化分配的位置的观点,由于编译的时候会自动内存逃逸处理,懂的大佬帮忙补充下:make、new内存分配是在堆上还是在栈上?
2、数组和切片的差异 (基本必问)相同点:
1)只能存储一组相同类型的数据构造
2)都是通过下标来访问,并且有容量长度,长度通过 len 获取,容量通过 cap 获取
差异:
1)数组是定长,访问和复制不能超过数组定义的长度,否则就会下标越界,切片长度和容量可以自动扩容
2)数组是值类型,切片是引用类型,每个切片都引用了一个底层数组,切片本身不能存储任何数据,都是这底层数组存储数据,以是修正切片的时候修正的是底层数组中的数据。切片一旦扩容,指向一个新的底层数组,内存地址也就随之改变
简洁地回答:
1)定义办法不一样
2)初始化办法不一样,数组须要指定大小,大小不能改变
3)在函数通报中,数组切片都是值通报。
数组的定义
var a1 [3]int
var a2 [...]int{1,2,3}
切片的定义
var a1 []int
var a2 :=make([]int, 3, 5)
数组的初始化
a1 := [...]int{1,2,3}
a2 := [5]int{1,2,3}
切片的初始化
b:= make([]int, 3, 5)
3、for range 的时候它的地址会发生变革么?答:在 for a,b := range c 遍历中, a 和 b 在内存中只会存在一份,即之后每次循环时遍历到的数据都因此值覆盖的办法赋给 a 和 b,a,b 内存地址始终不变。由于有这个特性,for 循环里面如果开协程,不要直接把 a 或者 b 的地址传给协程。办理办法:在每次循环时,创建一个临时变量。
4、go defer,多个 defer 的顺序,defer 在什么机遇会修正返回值?浸染:defer延迟函数,开释资源,扫尾事情;如开释锁,关闭文件,关闭链接;捕获panic;
避坑指南:defer函数紧跟在资源打开后面,否则defer可能得不到实行,导致内存透露。
多个 defer 调用顺序是 LIFO(后入先出),defer后的操作可以理解为压入栈中
defer,return,return value(函数返回值) 实行顺序:首先return,其次return value,末了defer。defer可以修正函数终极返回值,修正机遇:有名返回值或者函数返回指针 参考:
【Golang】Go措辞defer用法大总结(含return返回机制)__奶酪的博客-CSDN博客
有名返回值
func b() (i int) {defer func() {i++fmt.Println("defer2:", i)}()defer func() {i++fmt.Println("defer1:", i)}()return i //或者直接写成return}func main() {fmt.Println("return:", b())}
函数返回指针
func c() int {var i intdefer func() {i++fmt.Println("defer2:", i)}()defer func() {i++fmt.Println("defer1:", i)}()return &i}func main() {fmt.Println("return:", (c()))}
5、uint 类型溢出问题
超过最大存储值如uint8最大是255
var a uint8 =255
var b uint8 =1
a+b = 0总之类型溢出会涌现难以猜想的事
6、能先容下 rune 类型吗?
相称int32
golang中的字符串底层实现是通过byte数组的,中笔墨符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码恰好是utf-8
byte 等同于int8,常用来处理ascii字符
rune 等同于int32,常用来处理unicode或utf-8字符
7、 golang 中解析 tag 是怎么实现的?反射事理是什么?(中高等肯定会问,比较难,须要自己多去总结)
type User struct {name string `json:name-field`age int}func main() {user := &User{"John Doe The Fourth", 20}field, ok := reflect.TypeOf(user).Elem().FieldByName("name")if !ok {panic("Field not found")}fmt.Println(getStructTag(field))}func getStructTag(f reflect.StructField) string {return string(f.Tag)}
Go 中解析的 tag 是通过反射实现的,反射是指打算机程序在运行时(Run time)可以访问、检测和修正它本身状态或行为的一种能力或动态知道给天命据工具的类型和构造,并有机会修正它。反射将接口变量转换成反射工具 Type 和 Value;反射可以通过反射工具 Value 还原本钱来的接口变量;反射可以用来修正一个变量的值,条件是这个值可以被修正;tag是啥:构造体支持标记,name string `json:name-field` 便是 `json:name-field` 这部分
gorm json yaml gRPC protobuf gin.Bind()都是通过反射来实现的
8、调用函数传入构造体时,该当传值还是指针? (Golang 都是传值)Go 的函数参数通报都是值通报。所谓值通报:指在调用函数时将实际参数复制一份通报到函数中,这样在函数中如果对参数进行修正,将不会影响到实际参数。参数通报还有引用通报,所谓引用通报是指在调用函数时将实际参数的地址通报到函数中,那么在函数中对参数所进行的修正,将影响到实际参数
由于 Go 里面的 map,slice,chan 是引用类型。变量区分值类型和引用类型。所谓值类型:变量和变量的值存在同一个位置。所谓引用类型:变量和变量的值是不同的位置,变量的值存储的是对值的引用。但并不是 map,slice,chan 的所有的变量在函数内都能被修正,不同数据类型的底层存储构造和实现可能不太一样,情形也就不一样。
9、讲讲 Go 的 slice 底层数据构造和一些特性?答:Go 的 slice 底层数据构造是由一个 array 指针指向底层数组,len 表示切片长度,cap 表示切片容量。slice 的紧张实现是扩容。对付 append 向 slice 添加元素时,如果 slice 容量够用,则追加新元素进去,slice.len++,返回原来的 slice。当原容量不足,则 slice 先扩容,扩容之后 slice 得到新的 slice,将元素追加进新的 slice,slice.len++,返回新的 slice。对付切片的扩容规则:当切片比较小时(容量小于 1024),则采取较大的扩容倍速进行扩容(新的扩容会是原来的 2 倍),避免频繁扩容,从而减少内存分配的次数和数据拷贝的代价。当切片较大的时(原来的 slice 的容量大于或者即是 1024),采取较小的扩容倍速(新的扩容将扩大大于或者即是原来 1.25 倍),紧张避免空间摧残浪费蹂躏,网上实在很多总结的是 1.25 倍,那是在不考虑内存对齐的情形下,实际上还要考虑内存对齐,扩容是大于或者即是 1.25 倍。
(关于刚才问的 slice 为什么传到函数内可能被修正,如果 slice 在函数内没有涌现扩容,函数外和函数内 slice 变量指向是同一个数组,则函数内复制的 slice 变量值涌现变动,函数外这个 slice 变量值也会被修正。如果 slice 在函数内涌现扩容,则函数内变量的值会新天生一个数组(也便是新的 slice,而函数外的 slice 指向的还是原来的 slice,则函数内的修正不会影响函数外的 slice。)
10、讲讲 Go 的 select 底层数据构造和一些特性?(难点,没有项目常常可能说不清,口试一样平常会问你项目中怎么利用select)答:go 的 select 为 golang 供应了多路 IO 复用机制,和其他 IO 复用一样,用于检测是否有读写事宜是否 ready。linux 的系统 IO 模型有 select,poll,epoll,go 的 select 和 linux 系统 select 非常相似。
select 构造组成紧张是由 case 语句和实行的函数组成 select 实现的多路复用是:每个线程或者进程都先到注册和接管的 channel(装置)注册,然后壅塞,然后只有一个线程在运输,当注册的线程和进程准备好数据后,装置会根据注册的信息得到相应的数据。
select 的特性
1)select 操作至少要有一个 case 语句,涌现读写 nil 的 channel 该分支会忽略,在 nil 的 channel 上操作则会报错。
2)select 仅支持管道,而且是单协程操作。
3)每个 case 语句仅能处理一个管道,要么读要么写。
4)多个 case 语句的实行顺序是随机的。
5)存在 default 语句,select 将不会壅塞,但是存在 default 会影响性能。
11、讲讲 Go 的 defer 底层数据构造和一些特性?答:每个 defer 语句都对应一个_defer 实例,多个实例利用指针连接起来形成一个单连表,保存在 gotoutine 数据构造中,每次插入_defer 实例,均插入到链表的头部,函数结束再一次从头部取出,从而形成后进先出的效果。
defer 的规则总结:
延迟函数的参数是 defer 语句涌现的时候就已经确定了的。
延迟函数实行按照后进先出的顺序实行,即先涌现的 defer 末了实行。
延迟函数可能操作主函数的返回值。
申请资源后立即利用 defer 关闭资源是个好习气。
12、单引号,双引号,反引号的差异?单引号,表示byte类型或rune类型,对应 uint8和int32类型,默认是 rune 类型。byte用来强调数据是raw data,而不是数字;而rune用来表示Unicode的code point。
双引号,才是字符串,实际上是字符数组。可以用索引号访问某字节,也可以用len()函数来获取字符串所占的字节长度。
反引号,表示字符串字面量,但不支持任何转义序列。字面量 raw literal string 的意思是,你定义时写的啥样,它就啥样,你有换行,它就换行。你写转义字符,它也就展示转义字符。
二、map干系1、map 利用把稳的点,是否并发安全?map的类型是map[key],key类型的ke必须是可比较的,常日情形,会选择内建的基本类型,比如整数、字符串做key的类型。如果要利用struct作为key,要担保struct工具在逻辑上是不可变的。在Go措辞中,map[key]函数返回结果可以是一个值,也可以是两个值。map是无序的,如果我们想要担保遍历map时元素有序,可以利用赞助的数据构造,例如orderedmap。
第一,一定要先初始化,否则panic
第二,map类型是随意马虎发生并发访问问题的。不把稳就随意马虎发生程序运行时并发读写导致的panic。 Go措辞内建的map工具不是线程安全的,并发读写的时候运行时会有检讨,碰着并发问题就会导致panic。
2、map 循环是有序的还是无序的?无序的, map 因扩展⽽重新哈希时,各键值项存储位置都可能会发生改变,顺序自然也没法担保了,以是官方避免大家依赖顺序,直接打乱处理。便是 for range map 在开始处理循环逻辑的时候,就做了随机播种
3、 map 中删除一个 key,它的内存会开释么?(常问)如果删除的元素是值类型,如int,float,bool,string以及数组和struct,map的内存不会自动开释
如果删除的元素是引用类型,如指针,slice,map,chan等,map的内存会自动开释,但开释的内存是子元素运用类型的内存占用
将map设置为nil后,内存被回收。
这个问题还须要大家去搜索下答案,我记得有不一样的说法,谨慎采取本题答案。
4、怎么处理对 map 进行并发访问?有没有其他方案? 差异是什么?办法一、利用内置sync.Map,详细参考
https://mbd.baidu.com/ma/s/7Hwd9yMcmbd.baidu.com/ma/s/7Hwd9yMc
办法二、利用读写锁实现并发安全map
https://mbd.baidu.com/ma/s/qO7b0VQUmbd.baidu.com/ma/s/qO7b0VQU
5、 nil map 和空 map 有何不同?1)可以对未初始化的map进行取值,但取出来的东西是空:
var m1 map[string]string
fmt.Println(m1["1"])
////////////////////////////////////////////////////////////////////////////////
2)不能对未初始化的map进行赋值,这样将会抛出一个非常:
var m1 map[string]string
m1["1"] = "1"
panic: assignment to entry in nil map
////////////////////////////////////////////////////////////////////////////////
3) 通过fmt打印map时,空map和nil map结果是一样的,都为map[]。以是,这个时候别断定map是空还是nil,而该当通过map == nil来判断。
nil map 未初始化,空map是长度为空
6、map 的数据构造是什么?是怎么实现扩容?答:golang 中 map 是一个 kv 对凑集。底层利用 hash table,用链表来办理冲突 ,涌现冲突时,不是每一个 key 都申请一个构造通过链表串起来,而因此 bmap 为最小粒度挂载,一个 bmap 可以放 8 个 kv。在哈希函数的选择上,会在程序启动时,检测 cpu 是否支持 aes,如果支持,则利用 aes hash,否则利用 memhash。每个 map 的底层构造是 hmap,是有多少个构造为 bmap 的 bucket 组成的数组。每个 bucket 底层都采取链表构造。
hmap 的构造如下:
type hmap struct { count int // 元素个数 flags uint8 B uint8 // 扩容常量干系字段B是buckets数组的长度的对数 2^B noverflow uint16 // 溢出的bucket个数 hash0 uint32 // hash seed buckets unsafe.Pointer // buckets 数组指针 oldbuckets unsafe.Pointer // 构造扩容的时候用于赋值的buckets数组 nevacuate uintptr // 迁居进度 extra mapextra // 用于扩容的指针}
map 的容量大小底层调用 makemap 函数,打算得到得当的 B,map 容量最多可容纳 6.52^B 个元素,6.5 为装载因子阈值常量。装载因子的打算公式是:装载因子=填入表中的元素个数/散列表的长度,装载因子越大,解释空闲位置越少,冲突越多,散列表的性能会低落。底层调用 makemap 函数,打算得到得当的 B,map 容量最多可容纳 6.52^B 个元素,6.5 为装载因子阈值常量。装载因子的打算公式是:装载因子=填入表中的元素个数/散列表的长度,装载因子越大,解释空闲位置越少,冲突越多,散列表的性能会低落。
触发 map 扩容的条件
1)装载因子超过阈值,源码里定义的阈值是 6.5。
2)overflow 的 bucket 数量过多 map 的 bucket 定位和 key 的定位高八位用于定位 bucket,低八位用于定位 key,快速试错后再进行完全比拟
7、slices能作为map类型的key吗?
当时被问的一脸懵逼,实在是这个问题的变种:golang 哪些类型可以作为map key?
答案是:在golang规范中,可比较的类型都可以作为map key;这个问题又延伸到在:golang规范中,哪些数据类型可以比较?
不能作为map key 的类型包括:
slicesmapsfunctions详细参考:
golang 哪些类型可以作为map key
三、context干系1、context 构造是什么样的?context 利用场景和用场?
(难,也常常问你项目中怎么用,光靠记答案很难让口试官满意,反正有各种结合实际的问题)
参考链接:
go context详解 - 卷毛狒狒 - 博客园www.cnblogs.com/juanmaofeifei/p/14439957.html
答:Go 的 Context 的数据构造包含 Deadline,Done,Err,Value,Deadline 方法返回一个 time.Time,表示当前 Context 该当结束的韶光,ok 则表示有结束韶光,Done 方法当 Context 被取消或者超时时候返回的一个 close 的 channel,见告给 context 干系的函数要停滞当前事情然后返回了,Err 表示 context 被取消的缘故原由,Value 方法表示 context 实现共享数据存储的地方,是协程安全的。context 在业务中是常常被利用的,
其紧张的运用 :
1:高下文掌握,2:多个 goroutine 之间的数据交互等,3:超时掌握:到某个韶光点超时,过多久超时。
四、channel干系1、channel 是否线程安全?锁用在什么地方?就着图片里面的答案看看吧。
2、go channel 的底层实现事理 (数据构造)底层构造须要描述出来,这个大略,buf,发送行列步队,吸收行列步队,lock。
3、nil、关闭的 channel、有数据的 channel,再进行读、写、关闭会怎么样?(各种变种题型,主要)还要去理解一下单向channel,如只读或者只写通道常见的非常问题,这块还须要大家自己总结总结,有懂得大佬也可以评论发送答案。
4、向 channel 发送数据和从 channel 读数据的流程是什么样的?发送流程:
吸收流程:
这个没啥好说的,底层事理,1、2、3描述出来,担保口试官满意。详细的笔墨描述下面一题有,channel的观点多且繁芜,脑海中有个总分的观点,否则你说的再多,口试官也抓不住你说的重点,即是白说。问题5已经为大家总结好了。
5、讲讲 Go 的 chan 底层数据构造和紧张利用场景答:channel 的数据构造包含 qccount 当前行列步队中剩余元素个数,dataqsiz 环形行列步队长度,即可以存放的元素个数,buf 环形行列步队指针,elemsize 每个元素的大小,closed 标识关闭状态,elemtype 元素类型,sendx 行列步队下表,指示元素写入时存放到行列步队中的位置,recv 行列步队下表,指示元素从行列步队的该位置读出。recvq 等待读的 goroutine 行列步队,sendq 等待写的 goroutine 行列步队,lock 互斥锁,chan 不许可并发读写。
无缓冲和有缓冲差异: 管道没有缓冲区,从管道读数据会壅塞,直到有协程向管道中写入数据。同样,向管道写入数据也会壅塞,直到有协程从管道读取数据。管道有缓冲区但缓冲区没有数据,从管道读取数据也会壅塞,直到协程写入数据,如果管道满了,写数据也会壅塞,直到协程从缓冲区读取数据。
channel 的一些特点 1)、读写值 nil 管道会永久壅塞 2)、关闭的管道读数据仍旧可以读数据 3)、往关闭的管道写数据会 panic 4)、关闭为 nil 的管道 panic 5)、关闭已经关闭的管道 panic
向 channel 写数据的流程: 如果等待吸收行列步队 recvq 不为空,解释缓冲区中没有数据或者没有缓冲区,此时直接从 recvq 取出 G,并把数据写入,末了把该 G 唤醒,结束发送过程; 如果缓冲区中有空余位置,将数据写入缓冲区,结束发送过程; 如果缓冲区中没有空余位置,将待发送数据写入 G,将当前 G 加入 sendq,进入就寝,等待被读 goroutine 唤醒;
向 channel 读数据的流程: 如果等待发送行列步队 sendq 不为空,且没有缓冲区,直接从 sendq 中取出 G,把 G 中数据读出,末了把 G 唤醒,结束读取过程; 如果等待发送行列步队 sendq 不为空,此时解释缓冲区已满,从缓冲区中首部读出数据,把 G 中数据写入缓冲区尾部,把 G 唤醒,结束读取过程; 如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程;将当前 goroutine 加入 recvq,进入就寝,等待被写 goroutine 唤醒;
利用场景: 通报、过滤,旗子暗记广播,事宜订阅与广播,要求、相应转发,任务分发,结果汇总,并发掌握,限流,同步与异步
五、GMP干系1、什么是 GMP?(必问)答:G 代表着 goroutine,P 代表着高下文处理器,M 代表 thread 线程,在 GPM 模型,有一个全局行列步队(Global Queue):存放等待运行的 G,还有一个 P 确当地行列步队:也是存放等待运行的 G,但数量有限,不超过 256 个。GPM 的调度流程从 go func()开始创建一个 goroutine,新建的 goroutine 优先保存在 P 确当地行列步队中,如果 P 确当地行列步队已经满了,则会保存到全局行列步队中。M 会从 P 的行列步队中取一个可实行状态的 G 来实行,如果 P 确当地行列步队为空,就会从其他的 MP 组合窃取一个可实行的 G 来实行,当 M 实行某一个 G 时候发生系统调用或者壅塞,M 壅塞,如果这个时候 G 在实行,runtime 会把这个线程 M 从 P 中摘除,然后创建一个新的操作系统线程来做事于这个 P,当 M 系统调用结束时,这个 G 会考试测验获取一个空闲的 P 来实行,并放入到这个 P 确当地行列步队,如果这个线程 M 变成休眠状态,加入到空闲线程中,然后全体 G 就会被放入到全局行列步队中。
关于 G,P,M 的个数问题,G 的个数理论上是无限制的,但是受内存限定,P 的数量一样平常建议是逻辑 CPU 数量的 2 倍,M 的数据默认启动的时候是 10000,内核很难支持这么多线程数,以是全体限定客户忽略,M 一样平常不做设置,设置好 P,M 一样平常都是要大于 P。
2、进程、线程、协程有什么差异?(必问)进程:是运用程序的启动实例,每个进程都有独立的内存空间,不同的进程通过进程间的通信办法来通信。
线程:从属于进程,每个进程至少包含一个线程,线程是 CPU 调度的基本单位,多个线程之间可以共享进程的资源并通过共享内存等线程间的通信办法来通信。
协程:为轻量级线程,与线程比较,协程不受操作系统的调度,协程的调度器由用户运用程序供应,协程调度器按照调度策略把协程调度到线程中运行
3、抢占式调度是如何抢占的?基于协作式抢占
基于旗子暗记量抢占
就像操作系统要卖力线程的调度一样,Go的runtime要卖力goroutine的调度。当代操作系统调度线程都是抢占式的,我们不能依赖用户代码主动让出CPU,或者由于IO、锁等待而让出,这样会造成调度的不公正。基于经典的韶光片算法,当线程的韶光片用完之后,会被时钟中断给打断,调度器会将当前哨程的实行高下文进行保存,然后规复下一个线程的高下文,分配新的韶光片令其开始实行。这种抢占对付线程本身是无感知的,系统底层支持,不须要开拓职员分外处理。
基于韶光片的抢占式调度有个明显的优点,能够避免CPU资源持续被少数线程占用,从而使其他线程永劫光处于饥饿状态。goroutine的调度器也用到了韶光片算法,但是和操作系统的线程调度还是有些差异的,由于全体Go程序都是运行在用户态的,以是不能像操作系统那样利用时钟中断来打断运行中的goroutine。也得益于完备在用户态实现,goroutine的调度切换更加轻量。
上面这两段笔墨只是对调度的一个概括,详细的协作式调度、旗子暗记量调度大家还须要去详细理解,这偏底层了,大厂或者中高等开拓会问。(字节就问了)
4、M 和 P 的数量问题?p默认cpu内核数
M与P的数量没有绝对关系,一个M壅塞,P就会去创建或者切换另一个M,以是,纵然P的默认数量是1,也有可能会创建很多个M出来
【Go措辞调度模型G、M、P的数量多少得当?】
https://www.kancloud.cn/aceld/golang/1958305
GMP数量这一块,结论很好记,没用项目履历的话,问了项目中怎么用可能随意马虎卡壳。
六、锁干系1、除了 mutex 以外还有那些办法安全读写共享变量?将共享变量的读写放到一个 goroutine 中,其它 goroutine 通过 channel 进行读写操作。
可以用个数为 1 的旗子暗记量(semaphore)实现互斥
通过 Mutex 锁实现
2、Go 如何实现原子操作?答:原子操作便是不可中断的操作,外界是看不到原子操作的中间状态,要么看到原子操作已经完成,要么看到原子操作已经结束。在某个值的原子操作实行的过程中,CPU 绝对不会再去实行其他针对该值的操作,那么其他操作也是原子操作。
Go 措辞的标准库代码包 sync/atomic 供应了原子的读取(Load 为前缀的函数)或写入(Store 为前缀的函数)某个值(这里细节还要多去查查资料)。
原子操作与互斥锁的差异
1)、互斥锁是一种数据构造,用来让一个线程实行程序的关键部分,完成互斥的多个操作。
2)、原子操作是针对某个值的单个互斥操作。
3、Mutex 是悲观锁还是乐不雅观锁?悲观锁、乐不雅观锁是什么?悲观锁
悲观锁:当要对数据库中的一条数据进行修正的时候,为了避免同时被其他人修正,最好的办法便是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修正数据之前先锁定,再修正的办法被称之为悲观并发掌握【Pessimistic Concurrency Control,缩写“PCC”,别号“悲观锁”】。
乐不雅观锁
乐不雅观锁是相对悲观锁而言的,乐不雅观锁假设数据一样平常情形不会造成冲突,以是在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户非常信息,让用户决定如何去做。乐不雅观锁适用于读多写少的场景,这样可以提高程序的吞吐量
4、Mutex 有几种模式?1)正常模式
当前的mutex只有一个goruntine来获取,那么没有竞争,直接返回。新的goruntine进来,如果当前mutex已经被获取了,则该goruntine进入一个先入先出的waiter行列步队,在mutex被开释后,waiter按照前辈先出的办法获取锁。该goruntine会处于自旋状态(不挂起,连续霸占cpu)。新的goruntine进来,mutex处于空闲状态,将参与竞争。新来的 goroutine 有先天的上风,它们正在 CPU 中运行,可能它们的数量还不少,以是,在高并发情形下,被唤醒的 waiter 可能比较悲剧地获取不到锁,这时,它会被插入到行列步队的前面。如果 waiter 获取不到锁的韶光超过阈值 1 毫秒,那么,这个 Mutex 就进入到了饥饿模式。2)饥饿模式
在饥饿模式下,Mutex 的拥有者将直接把锁交给行列步队最前面的 waiter。新来的 goroutine 不会考试测验获取锁,纵然看起来锁没有被持有,它也不会去抢,也不会 spin(自旋),它会乖乖地加入到等待行列步队的尾部。 如果拥有 Mutex 的 waiter 创造下面两种情形的个中之一,它就会把这个 Mutex 转换成正常模式:
此 waiter 已经是行列步队中的末了一个 waiter 了,没有其它的等待锁的 goroutine 了;此 waiter 的等待韶光小于 1 毫秒。5、goroutine 的自旋占用资源如何办理自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断是否能够被成功获取,直到获取到锁才会退出循环。
自旋的条件如下:
1)还没自旋超过 4 次,
2)多核处理器,
3)GOMAXPROCS > 1,
4)p 受骗地 goroutine 行列步队为空。
mutex 会让当前的 goroutine 去空转 CPU,在空转完后再次调用 CAS 方法去考试测验性的霸占锁资源,直到不知足自旋条件,则终极会加入到等待行列步队里。
七、并发干系1、怎么掌握并发数?第一,有缓冲通道
根据通道中没有数据时读取操作陷入壅塞和通道已满时连续写入操作陷入壅塞的特性,恰好实现掌握并发数量。
func main() {count := 10 // 最大支持并发sum := 100 // 任务总数wg := sync.WaitGroup{} //掌握主协程等待所有子协程实行完之后再退出。c := make(chan struct{}, count) // 掌握任务并发的chandefer close(c)for i:=0; i<sum;i++{wg.Add(1)c <- struct{}{} // 浸染类似于waitgroup.Add(1)go func(j int) {defer wg.Done()fmt.Println(j)<- c // 实行完毕,开释资源}(i)}wg.Wait()}
第二,三方库实现的协程池
panjf2000/ants(比较火)
Jeffail/tunny
import ("log""time""github.com/Jeffail/tunny")func main() {pool := tunny.NewFunc(10, func(i interface{}) interface{} {log.Println(i)time.Sleep(time.Second)return nil})defer pool.Close()for i := 0; i < 500; i++ {go pool.Process(i)}time.Sleep(time.Second 4)}
2、多个 goroutine 对同一个 map 写会 panic,非常是否可以用 defer 捕获?
可以捕获非常,但是只能捕获一次,Go措辞,可以利用多值返回来返回缺点。不要用非常代替缺点,更不要用来掌握流程。在极个别的情形下,才利用Go中引入的Exception处理:defer, panic, recover Go中,对非常处理的原则是:多用error包,少用panic
defer func() {if err := recover(); err != nil {// 打印非常,关闭资源,退出此函数fmt.Println(err)}}()
3、如何优雅的实现一个 goroutine 池
(百度、手写代码,本人面传音控股被问道:要求数大于消费能力怎么设计协程池)
这一块能啃下来,offer满天飞,这该当是担保高并发系统稳定性、高可用的核心部分之一。
建议参考:
Golang学习篇--协程池_Word哥的博客-CSDN博客_golang协程池
https://blog.csdn.net/finghting321/article/details/106492915/
这篇文章的目录是:
1. 为什么须要协程池?
2. 大略的协程池
3. go-playground/pool
4. ants(推举)
以是直接研究ants底层吧,省的造轮子。
八、GC干系1、go gc 是怎么实现的?(必问)答:
细分常见的三个问题:1、GC机制随着golang版本变革如何变革的?2、三色标记法的流程?3、插入樊篱、删除樊篱,稠浊写樊篱(详细的实现比较难描述,但你要知道樊篱的浸染:避免程序运行过程中,变量被误回收;减少STW的韶光)4、虾皮还问了个开放性的题目:你以为往后GC机制会怎么优化?
Go 的 GC 回收有三次演进过程,Go V1.3 之前普通标记打消(mark and sweep)方法,整体过程须要启动 STW,效率极低。GoV1.5 三色标记法,堆空间启动写樊篱,栈空间不启动,全部扫描之后,须要重新扫描一次栈(须要 STW),效率普通。GoV1.8 三色标记法,稠浊写樊篱机制:栈空间不启动(全部标记成玄色),堆空间启用写樊篱,全体过程不要 STW,效率高。
Go1.3 之前的版本所谓标记打消是先启动 STW 停息,然后实行标记,再实行数据回收,末了停滞 STW。Go1.3 版本标记打消做了点优化,流程是:先启动 STW 停息,然后实行标记,停滞 STW,末了再实行数据回收。
Go1.5 三色标记紧张是插入樊篱和删除樊篱,写入樊篱的流程:程序开始,全部标记为白色,1)所有的工具放到白色凑集,2)遍历一次根节点,得到灰色节点,3)遍历灰色节点,将可达的工具,从白色标记灰色,遍历之后的灰色标记成玄色,4)由于并发特性,此刻外界向在堆中的工具发生添加工具,以及在栈中的工具添加工具,在堆中的工具会触发插入樊篱机制,栈中的工具不触发,5)由于堆中工具插入樊篱,则会把堆中玄色工具添加的白色工具改成灰色,栈中的玄色工具添加的白色工具依然是白色,6)循环第 5 步,直到没有灰色节点,7)在准备回收白色前,重新遍历扫描一次栈空间,加上 STW 停息保护栈,防止外界滋扰(有新的白色会被添加成玄色)在 STW 中,将栈中的工具一次三色标记,直到没有灰色,8)停滞 STW,打消白色。至于删除写樊篱,则是遍历灰色节点的时候涌现可达的节点被删除,这个时候触发删除写樊篱,这个可达的被删除的节点也是灰色,等循环三色标记之后,直到没有灰色节点,然后清理白色,删除写樊篱会造成一个工具纵然被删除了末了一个指向它的指针也依旧可以活过这一轮,不才一轮 GC 中被清理掉。
GoV1.8 稠浊写樊篱规则是:
1)GC 开始将栈上的工具全部扫描并标记为玄色(之后不再进行第二次重复扫描,无需 STW),2)GC 期间,任何在栈上创建的新工具,均为玄色。3)被删除的工具标记为灰色。4)被添加的工具标记为灰色。
2、go 是 gc 算法是怎么实现的? (得物,涌现频率低)func GC() {n := atomic.Load(&work.cycles)gcWaitOnMark(n)gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})gcWaitOnMark(n + 1)for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) {sweep.nbgsweep++Gosched()}for atomic.Load(&work.cycles) == n+1 && atomic.Load(&mheap_.sweepers) != 0 {Gosched()}mp := acquirem()cycle := atomic.Load(&work.cycles)if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) {mProf_PostSweep()}releasem(mp)}
底层事理了,可能大厂,中高等才会问,参考:
Golang GC算法解读_suchy_sz的博客-CSDN博客_go的gc算法
https://blog.csdn.net/shudaqi2010/article/details/90025192
3、GC 中 stw 机遇,各个阶段是如何办理的? (百度)底层事理,自行百度一下,我等渣渣简历都过不了BAT,字节,虾皮,特使拉以及一些国Q还能收到口试邀约。
1)在开始新的一轮 GC 周期前,须要调用 gcWaitOnMark 方法上一轮 GC 的标记结束(含扫描终止、标记、或标记终止等)。
2)开始新的一轮 GC 周期,调用 gcStart 方法触发 GC 行为,开始扫描标记阶段。
3)须要调用 gcWaitOnMark 方法等待,直到当前 GC 周期的扫描、标记、标记终止完成。
4)须要调用 sweepone 方法,扫描未肃清的堆跨度,并持续肃清,担保清理完成。在等待肃清完毕前的壅塞韶光,会调用 Gosched 让出。
5)在本轮 GC 已经基本完成后,会调用 mProf_PostSweep 方法。以此记录末了一次标记终止时的堆配置文件快照。
6)结束,开释 M。
4、GC 的触发机遇?低级必问,分为系统触发和主动触发。
1)gcTriggerHeap:当所分配的堆大小达到阈值(由掌握器打算的触发堆的大小)时,将会触发。
2)gcTriggerTime:当间隔上一个 GC 周期的韶光超过一定韶光时,将会触发。韶光周期以runtime.forcegcperiod 变量为准,默认 2 分钟。
3)gcTriggerCycle:如果没有开启 GC,则启动 GC。
4)手动触发的 runtime.GC 方法。
九、内存干系1、谈谈内存透露,什么情形下内存会透露?怎么定位排查内存泄露问题?答:go 中的内存泄露一样平常都是 goroutine 泄露,便是 goroutine 没有被关闭,或者没有添加超时掌握,让 goroutine 一只处于壅塞状态,不能被 GC。
内存透露有下面一些情形
1)如果 goroutine 在实行时被壅塞而无法退出,就会导致 goroutine 的内存泄露,一个 goroutine 的最低栈大小为 2KB,在高并发的场景下,对内存的花费也是非常胆怯的。
2)互斥锁未开释或者造成去世锁会造成内存泄露
3)time.Ticker 是每隔指定的韶光就会向通道内写数据。作为循环触发器,必须调用 stop 方法才会停滞,从而被 GC 掉,否则会一贯占用内存空间。
4)字符串的截取引发临时性的内存泄露
func main() {var str0 = "12345678901234567890"str1 := str0[:10]}
5)切片截取引起子切片内存泄露
func main() {var s0 = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 := s0[:3]}
6)函数数组传参引发内存泄露【如果我们在函数传参的时候用到了数组传参,且这个数组够大(我们假设数组大小为 100 万,64 位机上花费的内存约为 800w 字节,即 8MB 内存),或者该函数短韶光内被调用 N 次,那么可想而知,会花费大量内存,对性能产生极大的影响,如果短韶光内分配大量内存,而又来不及 GC,那么就会产生临时性的内存泄露,对付高并发场景相称恐怖。】
排查办法:
一样平常通过 pprof 是 Go 的性能剖析工具,在程序运行过程中,可以记录程序的运行信息,可以是 CPU 利用情形、内存利用情形、goroutine 运行情形等,当须要性能调优或者定位 Bug 时候,这些记录的信息是相称主要。
当然你能说说详细的剖析指标更加分咯,有的口试官就喜好他问什么,你简洁的回答什么,不喜好巴拉巴拉详细阐明一通,比如虾P口试官,不过他稽核的内容特殊多,可能是为了节约韶光。
2、知道 golang 的内存逃逸吗?什么情形下会发生内存逃逸?(必问)答:1)本该分配到栈上的变量,跑到了堆上,这就导致了内存逃逸。2)栈是高地址到低地址,栈上的变量,函数结束后变量会随着回收掉,不会有额外性能的开销。3)变量从栈逃逸到堆上,如果要回收掉,须要进行 gc,那么 gc 一定会带来额外的性能开销。编程措辞不断优化 gc 算法,紧张目的都是为了减少 gc 带来的额外性能开销,变量一旦逃逸会导致性能开销变大。
内存逃逸的情形如下:
1)方法内返回局部变量指针。
2)向 channel 发送指针数据。
3)在闭包中引用包外的值。
4)在 slice 或 map 中存储指针。
5)切片(扩容后)长度太大。
6)在 interface 类型上调用方法。
3、请简述 Go 是如何分配内存的?mcache mcentral mheap mspan
Go 程序启动的时候申请一大块内存,并且划分 spans,bitmap,areana 区域;arena 区域按照页划分成一个个小块,span 管理一个或者多个页,mcentral 管理多个 span 供现场申请利用;mcache 作为线程私有资源,来源于 mcentral。
这里描述的比较大略,你可以自己再去搜索下更简洁完全的答案。
4、Channel 分配在栈上还是堆上?哪些工具分配在堆上,哪些工具分配在栈上?Channel 被设计用来实现协程间通信的组件,其浸染域和生命周期不可能仅限于某个函数内部,以是 golang 直接将其分配在堆上
准确地说,你并不须要知道。Golang 中的变量只要被引用就一贯会存活,存储在堆上还是栈上由内部实现决定而和详细的语法没有关系。
知道变量的存储位置确实和效率编程有关系。如果可能,Golang 编译器会将函数的局部变量分配到函数栈帧(stack frame)上。然而,如果编译器不能确保变量在函数 return 之后不再被引用,编译器就会将变量分配到堆上。而且,如果一个局部变量非常大,那么它也该当被分配到堆上而不是栈上。
当前情形下,如果一个变量被取地址,那么它就有可能被分配到堆上,然而,还要对这些变量做逃逸剖析,如果函数 return 之后,变量不再被引用,则将其分配到栈上。
5、先容一下大工具小工具,为什么小工具多了会造成 gc 压力?小于即是 32k 的工具便是小工具,其它都是大工具。一样平常小工具通过 mspan 分配内存;大工具则直接由 mheap 分配内存。常日小工具过多会导致 GC 三色法花费过多的 CPU。优化思路是,减少工具分配。
小工具:如果申请小工具时,创造当前内存空间不存在空闲跨度时,将会须要调用 nextFree 方法获取新的可用的工具,可能会触发 GC 行为。
大工具:如果申请大于 32k 以上的大工具时,可能会触发 GC 行为。
十、其他问题1、Go 多返回值怎么实现的?答:Go 传参和返回值是通过 FP+offset 实现,并且存储在调用函数的栈帧中。FP 栈底寄存器,指向一个函数栈的顶部;PC 程序计数器,指向下一条实行指令;SB 指向静态数据的基指针,全局符号;SP 栈顶寄存器。
2、讲讲 Go 中主协程如何等待别的协程退出?答:Go 的 sync.WaitGroup 是等待一组协程结束,sync.WaitGroup 只有 3 个方法,Add()是添加计数,Done()减去一个计数,Wait()壅塞直到所有的任务完成。Go 里面还能通过有缓冲的 channel 实现其壅塞等待一组协程结束,这个不能担保一组 goroutine 按照顺序实行,可以并发实行协程。Go 里面能通过无缓冲的 channel 实现其壅塞等待一组协程结束,这个能担保一组 goroutine 按照顺序实行,但是不能并发实行。
啰嗦一句:循环智能二面,手写代码部分时,三个协程按交替顺序打印数字,末了题目做出来了,问我代码中Add()是什么意思,我回答的不是很清晰,这家公司就没有然后了。Add()表示协程计数,可以一次Add多个,如Add(3),可以多次Add(1);然后每个子协程必须调用done(),这样才能担保所有子协程结束,主协程才能结束。
3、Go 措辞中不同的类型如何比较是否相等?答:像 string,int,float interface 等可以通过 reflect.DeepEqual 和即是号进行比较,像 slice,struct,map 则一样平常利用 reflect.DeepEqual 来检测是否相等。
4、Go 中 init 函数的特色?答:一个包下可以有多个 init 函数,每个文件也可以有多个 init 函数。多个 init 函数按照它们的文件名顺序逐个初始化。运用初始化时初始化事情的顺序是,从被导入的最深层包开始进行初始化,层层递出末了到 main 包。不管包被导入多少次,包内的 init 函数只会实行一次。运用初始化时初始化事情的顺序是,从被导入的最深层包开始进行初始化,层层递出末了到 main 包。但包级别变量的初始化先于包内 init 函数的实行。
5、Go 中 uintptr 和 unsafe.Pointer 的差异?答:unsafe.Pointer 是通用指针类型,它不能参与打算,任何类型的指针都可以转化成 unsafe.Pointer,unsafe.Pointer 可以转化成任何类型的指针,uintptr 可以转换为 unsafe.Pointer,unsafe.Pointer 可以转换为 uintptr。uintptr 是指针运算的工具,但是它不能持有指针工具(意思便是它跟指针工具不能相互转换),unsafe.Pointer 是指针工具进走运算(也便是 uintptr)的桥梁。
6、golang共享内存(互斥锁)方法实现发送多个get要求待补充, 能看到这里还能给出答案的人,有吗????
7、从数组中取一个相同大小的slice有本钱吗?
或者这么问:从切片中取一个相同大小的数组有本钱吗?
这是爱立信的二面题目,这个问题我至今还没搞懂,不知道从什么切入点去剖析,欢迎指教。
PS:爱立信口试都要英文自我介绍,以及问答,如果英文回答不上来,会直接切换成中文。
8、PHP能实现并发处理事务吗?多进程:pcntl扩展
php pcntl用法-PHP问题-PHP中文网www.php.cn/php-ask-473095.html
多线程:
1)swoole(常用,生态完善)
2)pthread扩展(不常用)
为什么php多线程没人用?23 赞许 · 18 评论回答
参考并致谢
1、可可酱 可可酱:Golang常见面试题
2、Bel_Ami同学 golang 口试题(从根本到高等)