Golang八股文面试题

发布时间:2023年12月19日

1、golang 中 make 和 new 的区别?(基本必问)

1.作用变量类型不同,new可以给任意类型分配
内存,make给slice,map,channel分配内存;
2.返回类型不一样,new返回指向变量的指针,
make返回变量类型本身;
3.内存操作不一样,new分配空间后内存被清零。
 make 分配空间后会进行内存初始化;

2、数组和切片的区别 (基本必问)

1.内部结构
数组在内存中是一段连续的内存空间,元素的类型
和长度都是固定的。切片在内存中由一个指向底层
数组的指针、长度和容量组成,长度表示切片当前
包含的元素个数,容量表示切片可以容纳的最多元
素个数
2.长度
数组的长度在创建时指定,不能更改。切片的长度
可以动态扩展或收缩,可以根据需要自由调整。
3.使用方式
数组在使用时需要明确指定下标访问元素,不能动
态生成。切片可以使用 append 函数向其末尾添
加元素,可以使用 copy 函数复制切片,也可以
使用 make 函数创建指定长度和容量的切片

3、for range 的时候它的地址会发生变化么?for 循环遍历 slice 有什么问题?

1.地址没有发生变化-所有循环出来的值共用
2.在使用 for range 语句遍历切片或数组时,
每次迭代都会返回该元素的副本,而不是该元素
的地址。这意味着每次循环完成后,该元素所对
应的地址都是相同的即最初创建的(因为共用一个
变量所以后面的总会覆盖前面的值),并不会改变
3.在for range中,迭代变量会被重复使用,而
不是在每次迭代中创建一个新的变量,这个设计
选择有助于减少内存分配和提高性能.因此在循
环结束后,可能会遇到保存的值都是最后一次迭
代的值的情况.如果你需要在循环中保存每次迭
代的值,可以考虑在每次迭代中创建一个新的变
量来保存值而不是直接使用迭代变量。

//错误的写法	
	for _, v := range studs {
		gl[v.Name] = &v
	}
//正确的写法
for _, v := range studs {
	temp := v
	gl[v.Name] = &temp
}	

4、go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?(for defer)defer recover 的问题?(主要是能不能捕获)

1.在Go语言中,defer关键字用于延迟执行一个
函数调用,通常用于确保在函数执行结束后释放
资源或执行一些清理操作。多个defer语句按照
后进先出(Last In, First Out,LIFO)的
顺序执行,即最后一个defer语句会最先执行,
倒数第二个会在倒数第一个之后执行,以此类推。
2.defer 在何时修改返回值: defer语句中
的函数调用是在包含它的函数执行完毕之后才执
行的。如果包含defer的函数有命名的返回值,
并且在defer语句执行时修改了这个返回值,那
么最终的返回值将是defer语句中修改后的值。

func example() (result int) {
    defer func() {
        result += 10
    }()
    return 5
}
最终的返回值是15。
3.defer与recover通常一起使用,用于处理
函数中的错误。recover只能捕获在同一个
goroutine中发生的panic,而且必须在defer
中调用。如果recover在没有发生panic的情况
下调用,它会返回nil
4.defer和recover是Go语言中用于处理资源
释放和错误恢复的重要机制。

5、 uint 类型溢出

Golang的uint类型溢出问题通常会在大量运算
中发生,特别是在涉及到大量循环或者大数运算时
当uint类型的值超过其最大值时,它会发生溢出,
然后从该类型的最小值开始循环,解决方案:
1.使用更大的数据类型:例如,如果你正在使用
uint32,你可以尝试升级到uint64。这将提供
更大的值范围,从而减少溢出的可能性。
2.添加溢出检查:在每次运算之后,你可以检查
结果是否小于任一操作数(假设我们只在正数上
进行操作)。如果是这样,那么就发生了溢出。
3.使用 math/big 包:对于非常大的数值,你
也可以考虑使用 math/big 包中的 Int 类型
这个类型可以处理任意大小的数值,但是运算速
度会慢一些。

6、介绍 rune 类型

1.相当int32,它用来区分字符值和整数值。
2.golang中的字符串底层实现是byte数组,
中文字符在unicode下占2个字节,在utf-8占
3个字节,而golang默认编码正好是utf-8
3.byte 等同于int8,常用来处理ascii字符
4.rune 等同于int32,常用来处理unicode或
utf-8字符

func main() {
    str := "米虫 is cool"
    fmt.Println("STR LEN - ", len([]rune(str)))
}

7、 golang 中解析 tag 是怎么实现的?反射原理是什么?(问的很少,但是代码中用的多)

计算机程序在运行时(Run time)可以访问、检
测和修改它本身状态或行为的一种能力或动态知道
给定数据对象的类型和结构,并有机会修改它。

8、调用函数传入结构体时,应该传值还是指针? (Golang 都是值传递)

1.结构体的大小:如果结构体非常大,使用指针传
递会更有效率,因为这样只会复制指针值(一般是
8字节),而不是复制整个结构体。如果结构体小,
值传递和指针传递的性能差异可以忽略不计
2.是否需要修改原始结构体:如果需要在函数中
修改原始结构体,应该使用指针传递。如果使用
值传递,函数会接收结构体的一个副本,在函数
中对结构体的修改不会影响到原始的结构体。

9、Slice 介绍

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}
array 指向底层数组的指针
len 表示切片长度
cap 表示切片容量

2.make创建slice时可以指定其长度和容量,
底层会分配一个数组,数组的长度即容量。
slice = make([]int,5,10) 表示该slice
长度为5,容量为10。使用数组来创建slice时
slice与原数组共用一部分内存。
3.在使用append向slice追加元素时,若slice
空间不足则会发生扩容,扩容会重新分配一块更大
的内存,将原slice拷贝到新slice,再将数据追
加进去然后返回新slice。扩容操作只针对容量,
扩容后的slice长度不变
4.使用copy内置函数拷贝两个切片时,会将源切
片的数据逐个拷贝到目的切片指向的数组中,拷贝
数量取两个切片长度的最小值.也就是说copy不
会发生扩容.根据数组或切片来生成新的切片一般
使用slice := array[start:end]方式,这
种新生成的切片没有指定容量,新切片的容量是从
start开始到array的结束(注意并不是到 end)
另一种写法,生成新切片同时指定其容量:
slice[start:end:cap] ,其中的 cap 为新
切片的容量,容量不能超过原切片实际值。
5.创建切片可根据实际需要预分配容量,尽量
避免追加过程中扩容操作,有利于提升性能.

10、go struct 能不能比较?

在Go语言中,结构体(Struct)是否可以比较取决
于其字段。如果结构体的所有字段都是可比较的
那么这个结构体也是可比较的。这意味着你可以
使用 ==!= 运算符来比较两个结构体变量。

11、context 结构是什么样的?

type Context interface {
//当context被取消或者到了deadline 
//返回一个被关闭的 channel
Done() <-chan struct{}
//在channel Done关闭后,
//返回 context 取消原因
Err() error
// 返回 context 是否会被取消以及
//自动取消时间(即 deadline)
Deadline() (deadline time.Time, ok bool)
// 获取 key 对应的 value
Value(key interface{}) interface{}
}
Context是一个接口,定义了4个方法,它们都是
幂等的。也就是说连续多次调用同一个方法,
得到的结果都是相同的。
1.Done()方法当 Context被取消或者超时时候
返回的一个close的channel,告诉给context
相关的函数要停止当前工作,然后返回了
2.Err()返回一个错误,表示channel被关闭
的原因例如是被取消,还是超时。
3.Deadline()方法返回一个 time.Time,表示
当前 Context 应该结束的时间,ok 则表示有结
束时间,
4.Value()获取之前设置的key对应的value。

12、context 使用场景和用途?(基本必问)

context提供了一种在不同goroutine之间传递
取消信号、截止时间、截止日期等元数据的方法。
context主要用于在大规模的并发或分布式系统
中进行取消操作、截止时间管理、跟踪请求以及
传递其他请求范围的数据。以下是一些context
的常见使用场景和用途:
1.控制请求的超时和取消:context包允许在请求
超时或取消时通知Goroutine停止执行,以避免资
源泄漏和长时间阻塞.通过使用
context.WithTimeout 或 
context.WithCancel 创建一个带有超时或
取消功能的上下文,可以在超时或取消时终
止相关操作。

2.传递请求范围的值:context包允许在请
求范围内传递值.这对于在请求处理流程中共
享上下文信息非常有用,通过使用 
context.WithValue创建一个带有键值对
的上下文,可以将该值与上下文关联,并在
整个请求处理过程中访问这些值。

3.多个 Goroutine 之间传递上下文信息: 
context包允许在多个Goroutine之间传
递上下文信息。当启动一个Goroutine并
希望它能够访问请求的上下文时,可以将上
下文作为参数传递给 Goroutine 或使用 
context.WithValue创建一个新的上下文。

4.中止并发操作:当需要中止一组并发操作
时,可以使用context包实现。通过创建一
个带有取消功能的上下文,并将该上下文传
递给多个Goroutine,可以在需要时取消或
终止这些 Goroutine 的执行

13、channel 是否线程安全?锁用在什么地方?

Channel本身是线程安全的。在Go语言的并发
模型中,Channel是用来在不同的Goroutine
之间进行数据通信的,当一个Goroutine向
Channel发送数据时,直到另一个Goroutine接
收到这个数据之前,该Goroutine将会被阻塞。
这种机制保证了Channel的数据在Goroutine
之间传递时的安全性。在对buf中的数据进行入
队和出队操作时,为当前chnnel使用了互斥锁,
防止多个线程并发修改数据

14、go channel 的底层实现原理 (数据结构)

type hchan struct {
//channel 中元素个数
    qcount   uint             
//channel 中循环队列的长度 
    dataqsiz uint           
//channel 缓冲区数据指针
    buf      unsafe.Pointer 
//channel 发送操作处理到的位置     
    sendx    uint            
//channel 接收操作处理到的位置 
    recvx    uint       
//channel 中能够收发的每个元素大小 
    elemsize uint16           
//channel 中能够收发的每个元素类型    
    elemtype *_type   
// 是否已close     
    closed   uint32                    
//当前channel缓冲区不足而阻塞的goroutine列表 
    recvq    waitq           
//当前channel缓冲区不足而阻塞的goroutine列表  
    sendq    waitq            
// 互斥锁,保护所有字段
    lock mutex                
}
1.dataqsiz(缓冲区大小)对于无缓冲的
channel,dataqsiz字段的值为0,表示没有
分配数据缓冲区,数据的发送和接收是直接进
行的,需要发送和接收Goroutine同时准备好,
否则会阻塞.对于有缓冲的channel,dataqsiz
字段的值表示分配的数据缓冲区的大小。缓冲区
的大小决定了channel可以存储的元素数量。
当缓冲区完全填满时,继续向channel发送数据
会阻塞发送Goroutine,当缓冲区为空时,尝试从
 channel 接收数据会阻塞接收 Goroutine。
2.buf字段的作用是存储channel的实际数据
缓冲区.对于无缓冲的channel,buf字段为nil,
表示没有分配缓冲区,数据发送和接收操作是直
接阻塞的。对于有缓冲的channel,buf字段指
向分配的内存,用于存储元素值
3.recvq(接收队列)是一个用于存储等待接收
数据的Goroutine的队列.当一个Goroutine
尝试从空的channel接收数据时,它会被放置在
recvq中等待其他Goroutine向channel发送
数据.一旦有数据可用,被阻塞的Goroutine就
会从recvq中被唤醒,并继续执行
4.sendq(发送队列)是一个用于存储等待发送
数据的Goroutine的队列.当一个Goroutine
尝试向一个已满的channel发送数据时,它会被
放置在sendq中等待其他Goroutine从channel
接收数据.一旦channel有足够的空间可以接收
数据,被阻塞的Goroutine就会从sendq中被唤
醒,并继续执行发送操作。
5.waitq(等待队列)是一个用于存储等待在
channel上进行发送或接收操作的Goroutine
的队列.当一Goroutine尝试向已满的channel
发送数据或从空的channel接收数据时,它会被
放置在waitq中等待其他Goroutine执行相反
的操作,从而使发送或接收操作能够进行.一旦
有对应的发送或接收操作完成,等待的
Goroutine就会从waitq中被唤醒,并继续执行

15、 nil、关闭的 channel 再进行读、写、关闭会怎么样

1.nil channel:未初始化channel,未经make
2.closed channel:执行了closed的channel
3.对nil channel的读写会永久block
4.向closed channel写入会发生panic
5.从closed channel读取仍然可以读取剩余
的数据,直到数据全部读取完成立即读出零值
6.关闭nil channel,发生panic

16、 向 channel 发送数据和从 channel 读数据的流程是什么样的?

1.向通道(channel)发送数据和从通道读取
数据是 Go 语言中实现并发通信的重要机制。
2.向通道发送数据,创建通道: 使用 make 
函数创建一个通道,指定通道的类型和容量
发送数据:使用<-操作符将数据发送到通道中。
3.创建通道:使用make函数创建一个通道,指定
通道的类型和容量读取数据:使用 <- 操作符
从通道中读取数据。
4.通道是阻塞的,如果没有接收者,发送操作
将被阻塞;如果没有发送者,接收操作也将
被阻塞。这种机制确保了 goroutine 之
间的同步和通信

17、map 使用注意的点,并发安全?

1.map是引用类型,如果两个map同时指向一个
底层,那么一个map的变动会影响到另一个map。

2.map的零值是nil,对nil map进行任何添
加元素的操作都会触发运行时错误(panic)
因此,使用前必须使用make先创建map,
m := make(map[string]int)3.map的键可以是任何可以用==!=操作符
比较的类型,如字符串,整数,浮点数,复数,
布尔等,但是slice,map,和function类
型不可以作为map的键

4.map在使用过程中不保证遍历顺序,即:map
的遍历结果顺序可能会不一样

5.map进行的所有操作,包括读取,写入,删除,
都是不安全的,也就是说,如果你在一个
goroutine中修改map,同时在另一个
goroutine中读取map会触发错误:
“concurrent map read and map write”

6.Go语言的map不是并发安全的。并发情况下,
对map的读和写操作需要加锁,否则可能会因为
并发操作引起的竞态条件导致程序崩溃。为了在
并发环境下安全使用map,可以使用Go语言的
sync包中的sync.RWMutex读写锁,或者使用
sync.Map。

18、map 循环是有序的还是无序的?

for range循环map是无序的,因为map扩容?
重新哈希时,各键值项存储位置都可能会发生改
变,顺序自然也没法保证了,所以官方为了避免
依赖顺序,直接打乱处理。在进行循环遍历的
时候,生成了一个随机数作为遍历开始的位置,
可以for range循环map取出所有的key,
sort.Strings(keys)排序所有的keys再
循环所有的keys.

19、 map 中删除一个 key,它的内存会释放么?

1.如果删除的元素是值类型,如int,float,
bool,string以及数组和struct,map的内
存不会自动释放
2.如果删除的元素是引用类型,如指针,slice,
map,chan等,map的内存会自动释放,但释放的
内存是子元素应用类型的内存占用
3.将map设置为nil后,内存被回收

20、怎么处理对 map 进行并发访问?有没有其他方案?

1.对整个map加上读写锁sync.RWMutex虽然
解决问题但是锁粒度大,影响性能
2.1操作会导致整个map被锁住,导致性能降低
所以提出了分片思想,将一个map分成几个片,
按片加锁。第三方包实现:
github.com/orcaman/concurrent-map
3.标准库中的sync.Map是专为append-only
场景设计的。sync.Map在读多写少性能比较好
否则并发性能很差。

21、 nil map 和空 map 有何不同?

Nil Map是指未初始化的Map或者是指被赋值
为nil 的Map 对象,
empty Map是指已经初始化,但是没有任何
元素的Map 对象。

22、map 的数据结构是什么?是怎么实现扩容?

type hmap struct {
// 元素数量
	count     int
//状态标识,比如被写,被遍历等	
	flags     uint8
//桶(bmap)数量的对数,
//也就是说桶的数量是2^B个	
	B         uint8
	noverflow uint16
//哈希种子,增加哈希函数的随机性	
	hash0     uint32
//指向bmap数组的指针	
	buckets    unsafe.Pointer
//指向旧bmap数组的指针,与扩容有关	
	oldbuckets unsafe.Pointer
//表示扩容进度	
	nevacuate  uintptr
	extra *mapextra 
}
1.触发扩容有两种情况:
第一种:负载因子超过了默认值,就是正常元
素过多造成的扩容-增量扩容
负载因子=元素数量除桶数量,默认为6.5
第二种:overflow的bucket数量过多,就
是因为元素不断的进行增删造成溢出桶很多
元素很少,没有满足负载因子的默认值,但
是效率很低--等量扩容
2.golang通过拉链发解决哈希冲突(开放寻址法)

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

在Go语言中,map为引用类型,所以在修改map内
的值时,原数据也会随之变化,因此当你通过一个
key 取出一个值并对其进行修改时,将会影响到
原map 中该key 对应的值。

24、什么是 GMP?(必问)调度过程是什么样的?

1.G - Goroutine也就是协程,是用户态
轻量级线程,一个goroutine大概需要2k
内存,在 64 位的机器上,可以轻松创建
几百万goroutine
2.M - Machine Thread,也就是操作系
统线程,go runtime 最多允许创建1万个
操作系统线程,超过了就会抛出异常
3.P - Processor逻辑处理器,默认数量
等于cpu核心数,通过环境变量GOMAXPROCS
改变其数量
4.全局队列(GlobalQueue)存放等待运行
的G,P还有个本地队列(也称为LRQ):存放等
待运行的G,但G的数量不超过256个。新建的
goroutine优先保存在P的本地队列中,如果
P的本地队列已满,则会保存到全局队列中
5.P包含了运行G所需要的资源,M想要运行
goroutine必须先获取P然后从P的本地队列
获取Goroutine,P队列为空的时候,M也会
尝试从全局队列拿一批Goroutine放到P的
本地队列,或者从其他P的本地队列偷取一半
放到自己P的本地队列,M不断通过P获取
Goroutine并执行,不断重复下去。如果M
阻塞或者不够了,会创建新的M来支持。比如
所有的M都被阻塞住了,而P中还有很多的就
绪任务,调度器就会去寻找空闲的M。找不
到的话,就会去创建新的M,总而言之一个M
阻塞P就会去创建或者切换另一个M,

25、进程、线程、协程有什么区别?(必问)

1.进程:是应用程序的启动实例,每个进程
都有独立的内存空间,不同的进程通过管道,
信号量,共享内存等方式来通信。
2.线程:从属于进程,每个进程至少包含一
个线程,线程是CPU调度的基本单位,多线程
之间可以共享进程的资源,并通过共享内存
等方式来通信。
3.协程:轻量级线程,与线程相比,协程不受
操作系统的调度,协程的调度器由用户程序
提供,协程调度器按照调度策略把协程调度
到线程中运行

26、抢占式调度是如何抢占的?

就像操作系统要负责线程的调度一样,Go的
runtime要负责goroutine的调度.现代
操作系统调度线程都是抢占式的,我们不能
依赖用户代码主动让出CPU,或者因为IO、
锁等待而让出,这样会造成调度的不公平。
基于经典的时间片算法,当线程的时间片用
完之后,会被时钟中断给打断,调度器会将
当前线程的执行上下文进行保存,然后恢
复下一个线程的上下文,分配新的时间片
令其开始执行。这种抢占对于线程本身是
无感知的,系统底层支持,不需要开发人
员特殊处理。
基于时间片的抢占式调度有个明显的优点,
能够避免CPU资源持续被少数线程占用,从
而使其他线程长时间处于饥饿状态。
goroutine的调度器也用到了时间片算法,
但是和操作系统的线程调度还是有些区别的,
因为整个Go程序都是运行在用户态的,所以
不能像操作系统那样利用时钟中断来打断运
行中的goroutine。也得益于完全在用户态
实现,goroutine的调度切换更加轻量。

27、怎么控制并发数?如何优雅的实现一个 goroutine 池

1.有缓冲通道,三方库实现的协程池
2. 为什么需要协程池,简单的协程池,
go-playground/pool, ants(推荐)

28、除了 mutex 以外还有那些方式安全读写共享变量?

1.将共享变量的读写放到一个goroutine中,
其它goroutine通过channel进行读写操作。
2.可以用个数为1的信号量(semaphore)实现互斥
3.通过 Mutex 锁实现

29、Go 如何实现原子操作?

1.原子操作就是不可中断的操作,外界是看不到原
子操作的中间状态,要么看到原子操作已经完成,要
么看到原子操作已经结束。在某个值的原子操作执
行的过程中,CPU绝对不会再去执行其他针对该值
的操作,那么其他操作也是原子操作。
2.Go语言的标准库代码包sync/atomic提供
了原子的读取(Load为前缀的函数或写入
(Store 为前缀的函数某个值
原子操作与互斥锁的区别
1)、互斥锁是一种数据结构,用来让一个线程执
行程序的关键部分,完成互斥的多个操作。
2)、原子操作是针对某个值的单个互斥操作。

30、Mutex 是悲观锁还是乐观锁?悲观锁、乐观锁是什么?

1.悲观锁:当要对数据库中的一条数据进行修改的
时候,为了避免同时被其他人修改,最好的办法就是
直接对该数据进行加锁以防止并发.这种借助数据库
锁机制,在修改数据之前先锁定,再修改的方式被称
之为悲观并发控制
2.乐观锁是相对悲观锁而言的,乐观锁假设数据一般
情况不会造成冲突,所以在数据进行提交更新的时候
才会正式对数据的冲突与否进行检测,如果冲突,则
返回给用户异常信息,让用户决定如何去做。乐观锁
适用于读多写少的场景,这样可以提高程序的吞吐量

31、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 毫秒。

32、goroutine 的自旋占用资源如何解决

自旋锁是指当一个线程在获取锁的时候,如果锁
已经被其他线程获取,那么该线程将循环等待,
然后不断地判断是否能够被成功获取,直到获取
到锁才会退出循环。
自旋的条件如下:
1)还没自旋超过 4 次,
2)多核处理器,
3)GOMAXPROCS > 14)p 上本地 goroutine 队列为空。
mutex 会让当前的 goroutine 去空转 CPU,
在空转完后再次调用 CAS 方法去尝试性的占有
锁资源,直到不满足自旋条件,则最终会加入到
等待队列里。

33、go三色标记法(必问)

1.起初所有的对象都是白色的;
2.从根对象出发扫描所有可达对象,标记为灰色,
放入待处理队列;
3.从待处理队列中取出灰色对象,将其引用的对
象标记为灰色并放入待处理队列中,自身标记为
黑色;
4.重复步骤(3),直到待处理队列为空,此时
白色对象即为不可达的“垃圾”,回收白色对象;
5.屏障机制-STW垃圾回收过程中为了保证准确
性防止无止境的内存增长等问题而不可避免的需
要停止赋值器进一步操作对象图以完成垃圾回收。
STW时间越长,对用户代码造成的影响越大

34、知道哪些 sync 同步原语?各有什么作用?

1.sync.Mutex
它允许在共享资源上互斥访问(不能同时访问)
2.sync.RWMutex
是一个读写互斥锁
3.sync.WaitGroup
sync.WaitGroup拥有一个内部计数器。当计
数器等于0时,则Wait()方法会立即返回。否
则它将阻塞执行Wait()方法的goroutine直
到计数器等于0时为止。要增加计数器,使用
Add(int)方法。要减少它,使用Done()
也可以传递负数给Add方法把计数器减少指定
大小,Done()方法底层就是通过Add(-1)
实现的。
4.sync.Map
是一个并发版本的Go语言的map
Store-添加元素。
Load-检索元素。
Delete-删除元素。
LoadOrStore检索或添加之前不存在的元素。
如果键之前在map中存在,则返回的布尔值为true。
使用Range遍历元素。
5.sync.Pool
是一个并发池,负责安全地保存一组对象。
Get() 用来从并发池中取出元素。
Put将一个对象加入并发池。
6.sync.Once
可确保一个函数仅执行一次
7.sync.Cond
它用于发出信号(一对一)
或广播信号(一对多)到goroutine

35、知道 golang 的内存逃逸吗?什么情况下会发生内存逃逸?

1)本该分配到栈上的变量,跑到了堆上,
这就导致了内存逃逸。
2)栈是高地址到低地址,栈上的变量,
函数结束后变量会跟着回收掉,不会有
额外性能的开销。
3)变量从栈逃逸到堆上,如果要回收掉,
需要进行gc,那么gc一定会带来额外的
性能开销。编程语言不断优化gc算法,
主要目的都是为了减少gc带来的额外性
能开销,变量逃逸会导致性能开销变大。
内存逃逸的情况如下:
1)方法内返回局部变量指针。
2)向 channel 发送指针数据。
3)在闭包中引用包外的值。
4)在 slice 或 map 中存储指针。
5)切片(扩容后)长度太大。
6)在 interface 类型上调用方法。

36、K8s 含有哪些重要组成部分

1.Master节点:
API Server(API服务器):提供了Kubernetes 
	API,用于与Kubernetes集群进行通信,
	接受和处理各种命令。
Controller Manager(控制器管理器):负责运
	行控制器(如ReplicaSet Controller、
	DeploymentController),监控集群状态
	并根据所需状态进行调整。
Scheduler(调度器):负责将新创建的Pod调度
	到集群中的Node上,考虑资源需求,约束条
	件等因素。

2.Node节点:
Kubelet:负责在Node上管理容器,包括启动、
	停止、重启容器等。
Container Runtime(容器运行时):实际运行
	容器的软件,如Docker,containerd等。
Kube Proxy:负责维护网络规则并将网络流量
	转发到正确的容器服务。

3.Etcd:
分布式键值存储,用于保存集群的配置信息,
状态信息等,被Master和Node节点共同使用。

4.Pod:
最小部署单元,包含一个或多个容器.Pod中的容
器共享网络和存储,通常是紧密耦合的应用组件

5.Service:
提供了一种抽象,用于定义一组Pod的访问方式.
Service可以保证一组Pod的稳定网络访问。

6.Volume:
用于在Pod中持久化数据.Volume可以连接到
Node上的物理存储、网络存储等。

7.Namespace:
用于将Kubernetes集群划分为多个虚拟集群,
以便在同一集群中运行多个不同的应用,环境等

8.ConfigMap 和 Secret:
用于保存配置信息和敏感数据,可以在Pod中被
挂载为文件或环境变量。

9.Ingress:
提供HTTP和HTTPS路由到服务的规则,允许外部
流量访问Kubernetes集群中的服务。

10.StatefulSet:
用于部署有状态应用,确保Pod有唯一的标识和
稳定的网络标识标题

37、谈谈内存泄露,什么情况下内存会泄露?怎么定位排查内存泄漏问题?

go 中的内存泄漏一般都是goroutine泄漏,
就是 goroutine 没有被关闭,或者没有添
加超时控制,让 goroutine 一只处于阻塞
状态,不能被 GC

38、内存泄露有下面一些情况,以及解决方法

1)如果goroutine在执行时被阻塞而无法
退出,就会导致goroutine的内存泄漏,一个
goroutine 的最低栈大小为 2KB,在高并
发的场景下,对内存的消耗也是非常恐怖的。
2)互斥锁未释放或者造成死锁会造成内存泄漏
3)time.Ticker是每隔指定的时间就会向
通道内写数据。作为循环触发器,必须调用
stop 方法才会停止,从而被 GC 掉,否则
会一直占用内存空间。
4)字符串的截取引发临时性的内存泄漏
5) 切片截取引起子切片内存泄漏
6) 函数数组传参引发内存泄漏
一般通过pprof是Go的性能分析工具,在程序
运行过程中,可以记录程序的运行信息,可以
是CPU使用情况、内存使用情况、goroutine
运行情况等,当需要性能调优或者定位Bug时
候,这些记录的信息是相当重要

39、 MySQL 中的 B 树和 B+ 树区别

1.存储方式: B 树和 B+ 树的存储方式不同。
在 B 树中,每个节点存储键和对应的值,而在
B+ 树中,只有叶子节点存储键和指向数据的指
针,非叶子节点只存储键。这意味着 B+ 树的
内部节点可以容纳更多的键,减少了树的高度,
从而减少了磁盘访问次数,提高了查询性能

2.叶子节点结构:B树和B+树的叶子节点结构
也不同.在B树中,叶子节点包含了数据的实际
值,而在B+树中,叶子节点只包含键和指向数
据的指针.这使得B+树的叶子节点可以形成一
个有序链表,通过在链表上进行顺序遍历,可
以高效地获取范围内的数据。而 B 树则需要
在不同的层级进行跳跃,性能相对较低

4.适用场景: B 树适用于需要随机访问的场
景,例如数据库索引。而B+树更适合范围查
询和顺序访问的场景,例如文件系统索引

40、http三次握手-建立Tcp连接和四次挥手-释放tcp链接

第一次握手(SYN=1,seq=x):
客户端发送一个TCP的SYN标志位置1的包,
指明客户端打算连接的服务器的端口,以
及初始序号X,保存在包头的序列号
(Sequence Number)字段里。

第二次握手(SYN=1,ACK=1,seq=y,
ACKnum=x+1):服务器发回确认包
(ACK)应答。即SYN标志位和ACK标志位
均为1。服务器端选择自己的ISN序列号,
放在seq域里,同时将确认序号
(Acknowledgement Number)设置为
客户的ISN加1,即X+1。发送完毕后,服
务器端进入SYN_RCVD状态。

第三次握手(ACK=1,ACKnum=y+1):
客户端再次发送确认包(ACK),SYN标志
位为0,ACK标志位为1,并且把服务器发来
ACK的序号字段+1,放在确定字段中发送给
对方,并且在数据段放写ISN的+1。

在这里插入图片描述

第一次挥手(FIN=1,seq=x):
假设客户端想要关闭连接,客户端发送一个
FIN标志位置为1的包,表示自己已经没有数
据可以发送了,但是仍然可以接收数据。
发送完毕之后,客户端进入FIN_WAIT_1状态。

第二次挥手(ACK=1,ACKnum=x+1):
服务器端确认客户端的FIN包,发送一个确认
包,表明自己接收到了客户端关闭连接的请求,
但还没有准备好关闭连接。发送完毕后,服务
器端进入CLOSE_WAIT状态,客户端接收到这
个确认包之后进入FIN_WAIT_2状态,等待服
务器端关闭连接。

第三次挥手(FIN=1,seq=y):
服务器端准备好关闭连接时,向客户端发送结
束连接请求,FIN置为1。发送完毕后,服务
器端进入LAST_ACK状态,等待来自客户端的
最后一个ACK。

第四次挥手(ACK=1,ACKnum=y+1):
客户端接收到来自服务器的端的关闭请求,发
送一个确认包,并进入TIME_WAIT状态,等
待可能出现的要重传的ACK包。
服务器端接收到这个确认包之后,关闭连接,
进入CLOSED状态。客户端等待了某个固定时间
(两个最大段生命周期,2MSL,2Maximum Segment Lifetime)之后,
没有收到服务器端的ACK,认为服务器端已经
正常关闭连接,于是自己也关闭连接,进入CLOSED状态。

在这里插入图片描述

41、 tcp 和 http 的区别

HTTP 是无状态的短连接,是应用层协议
定义的是传输数据的内容的规范
TCP是有状态的长连接 TCP是传输层协议
定义的是数据传输和连接方式的规范

在这里插入图片描述

42、Mysql 常见sql优化

1、在where及order by涉及的列上建立索引。 
2、不要在where子句中进行的操作:
1>使用!=<>操作符,
2>对字段进行null值判断
3>使用or来连接条件
4>使用参数
5>对字段进行表达式操作,函数操作
6>对“=”左边进行函数、算术运算或其他表达式运算
7>in和not in的使用,用between或exists代替in
导致索引失效,扫描全表
3、like '%abc%' 导致索引失效 
4、不要使用 select * from t
6、不要在有大量数据重复的列上建立索引 
7、一个表的索引数不要超过6个,索引提高
select 的效率,同时也降低了insert 及
update 的效率,因为可能会重建索引
8、使用varchar代替char,因为变长字段存
储空间小,可以节省存储空间,其次对于查询
来说,在一个相对较小的字段内搜索效率
显然要高些。 
9、在使用索引字段作为条件时,如果该索引是
复合索引,那么必须使用到该索引中的第一个字
段作为条件时才能保证系统使用该索引,否则该
索引将不会被使用,并且应尽可能的让字段顺序
与索引顺序相一致。 
10、尽量使用数字型字段,若只含数值信息的
字段尽量不要设计为字符型 
11、尽量使用表变量来代替临时表。如果表变量
包含大量数据,请注意索引非常有限(只有主键索引)。 
12、避免频繁创建和删除临时表,以减少系统
表资源的消耗。 
13、临时表并不是不可使用,适当地使用它们
可以使某些例程更有效,例如,当需要重复引
用大型表或常用表中的某个数据集时。但是,
对于一次性事件,最好使用导出表。 
14、在新建临时表时,如果一次性插入数据量
很大,那么可以使用 select into 代替 
create table,避免造成大量 log ,以
提高速度;如果数据量不大,为了缓和系统表
的资源,应先create table,然后insert。 
15、如果使用到了临时表,在存储过程的最后
务必将所有的临时表显式删除,先 truncate 
table ,然后 drop table ,这样可以避免
系统表的较长时间锁定。 
16、尽量避免使用游标,因为游标的效率较差,
如果游标操作的数据超过1万行,那么就应该考
虑改写。 

43、mysql 事务实现原理

通过日志的记录和重做、回滚日志的使用、锁的
管理和控制,并发控制机制的应用实现。这些机
制共同确保了事务的原子性、一致性、隔离性和
持久性,保证了数据库的数据完整性和并发操作
的正确性
1. 日志(Log):
重做日志(Redo Log)**: 在事务进行修改操
作时,MySQL 将修改的内容记录到重做日志中,
以确保事务的修改可以持久化到磁盘。重做日志
是顺序写入的,因此具有较高的写入性能。
回滚日志(Undo Log)**: 在事务进行修改操
作之前,MySQL 将当前数据的副本记录到回滚日
志中。如果事务需要回滚,MySQL 可以使用回滚
日志将数据恢复到事务开始之前的状态。
2. 锁(Lock):
行级锁(Row-Level Locking)**: MySQL 
支持行级锁,允许在事务中对数据行进行加锁。
这样可以控制并发事务对同一数据的访问,确保
数据的一致性和隔离性。
锁粒度(Lock Granularity)**: MySQL 
的锁粒度可以根据具体情况调整,包括表级锁、
页级锁和行级锁。较小的锁粒度可以提高并发
性能,但也会增加锁管理的开销。
3. 隔离级别(Isolation Level)**: 
MySQL 支持多个隔离级别,例如:
读未提交(Read Uncommitted)、
读已提交(Read Committed)、
可重复读(Repeatable Read)
串行化(Serializable)。
隔离级别定义了事务之间的隔离程度,
决定了事务读取数据的一致性和并发性。
4. 并发控制(Concurrency Control)**: 
MySQL 使用并发控制机制来处理多个并发
事务对数据库的访问。这包括锁的获取与释
放、冲突检测和解决,以及事务的调度和执行。

44、mysql中 explain工具的使用

type 字段可能的值
ALL < index < range ~ index_merge < ref < eq_ref < const < system

在这里插入图片描述

45、Go 的 select 底层数据结构和一些特性?

1.go的 select 为golang提供了多路IO复用
机制,和其他IO复用一样,用于检测是否有读写事
件ready.linux的系统IO模型有select,poll,
epoll. select 结构组成主要是由 case 语
句和执行的函数组成
2.select 的特性
1)select操作至少要有一个 case 语句,出现
读写nil的channel该分支会忽略,在nil的
channel 上操作则会报错。
2)select仅支持管道,而且是单协程操作。
3)每个 case 语句仅能处理一个管道,要么读
要么写。
4)多个 case 语句的执行顺序是随机的。
5)存在default语句,select将不会阻塞,
但是存在 default 会影响性能。

46、单引号,双引号,反引号的区别?

1.单引号:表示byte类型或rune类型,对应
uint8和int32类型,默认是rune类型。

2.双引号:是字符串,实际上是字符数组。

3.反引号:表示字符串字面量,但不支持任何转义
序列

47、redis 常见的数据类型

1、string(字符串);
2、hash(哈希);
3、list(列表);
4、set(集合);
5、sort set (有序集合)

48、Innodb与Myisam引擎的区别

1.InnoDB 支持事务MyISAM 不支持
2.InnoDB 支持外键MyISAM 不支持
3.InnoDB 是聚集索引,MyISAM是非聚集索引
聚簇索引的文件存放在主键索引的叶子节点上,
因此 InnoDB必须要有主键,通过主键索引效率
很高.但是辅助索引需要两次查询,先查询到主键,
然后再通过主键查询到数据.因此主键不应该过大
因为主键太大,其他索引也都会很大。而MyISAM
是非聚集索引,数据文件是分离的,索引保存的
是数据文件的指针。主键索引和辅助索引是独立的
4.InnoDB 不保存表的具体行数,MyISAM保存了
整个表的行数
5.InnoDB 最小的锁粒度是行锁,MyISAM是表锁

49、 什么时候触发行级锁,表级锁

1.当执行DDL语句去修改表结构时,会使用表级锁。
2.一般情况下都会默认使用行级锁,貌似是需要有索引匹配到才行
文章来源:https://blog.csdn.net/weixin_38385580/article/details/135031009
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。