2024年Go最全Golang面试题整理(1),五年Golang开发者小米、阿里面经

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

函数传递指针不一定比传值效率高。传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以传递指针不一定是高效的。

所有对象默认都在栈中,当有下列情况时,对象逃逸到堆中:

  1. 对象作为函数的返回值返回,包括指针和值类型
  2. 对象申请的空间过大
  3. 对象申请空间未知
  4. 闭包

3、指针逃逸分析的作用?

在编译原理中,分析指针动态范围的方法称之为逃逸分析。通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了“逃逸”。

  • 逃逸分析的好处是为了减少gc的压力,不逃逸的对象分配在栈上,当函数返回时就回收了资源,不需要gc标记清除。
  • 逃逸分析完后可以确定哪些变量可以分配在栈上,栈的分配比堆快,性能好(逃逸的局部变量会在堆上分配 ,而没有发生逃逸的则有编译器在栈上分配)。
  • 同步消除,如果你定义的对象的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。

逃逸总结:

  • 栈上分配内存比在堆中分配内存有更高的效率。
  • 栈上分配的内存不需要GC处理。
  • 堆上分配的内存使用完毕会交给GC处理。
  • 逃逸分析目的是决定内分配地址是栈还是堆。
  • 逃逸分析在编译阶段完成。

4、Linux操作系统中线程有哪几种模型?

Linux多线程模型 - 知乎

5、Go垃圾回收,stop the world。

**Stop the world:**停止程序意味着停止所有正在运行的 goroutine。

  1. 抢占所有正在运行的 goroutine。一旦 goroutine 被抢占,它们将在安全点停止。同时,P 处理器将(正在运行的代码或在空闲列表)被标记为已停止,以不运行任何代码。
  2. Go 调度程序将运行,将每个 M 与其 P 各自分离,并将其放入空闲列表中。关于在每个M上运行的 goroutine,它们将在全局队列GRQ中等待。
  3. 一旦世界停止了,只有唯一活动的 goroutine 才能安全地运行,并在工作完成后启动整个世界。

6、PMG模型。

P:processor ,代表一个逻辑处理器,也就是执行代码的上下文环境。

M:machine ,代表一个内核线程(OS线程),这个线程是操作系统来处理的,操作系统负责把它放置到一个 core 上去执行。

G:goroutine ,代表一个并发的代码片段。

简而言之,P 在 M 上执行 G 。

LRQ:Local RunQueue,本地等待运行的 G,存的数量有限,不超过 256 个。如果队列满了,则会把本地队列中一半的 G 移动到全局队列。

GRQ:Global RunQueue,存放等待运行的 G,LRQ都满了的时候,会放入GRQ。其中,LRQ 不加锁,GRQ加锁。

Network poller:同步调度。

线程状态:

  • 等待(Waiting):线程停止并等待被唤醒。可能发生的原因有,等待硬件(硬盘、网络),操作系统(系统调用) 或者是同步调用(atomic,mutexes)。这些情况是导致性能问题的根源。
  • 可运行(Runnable):线程想要占用内核上的cpu时间来执行分配给线程的指令。如果你有许多线程想要cpu时间,线程必须要等一段时间才能取到cpu时间。随着更多线程争用cpu时间,线程分配的cpu时间会更短。这种情况下的调度延时也会造成性能问题。
  • 执行中(Executing):此时线程已经置于内核中,并且正在执行它的机器指令。应用程序的相关内容正在被处理。

线程的工作类型:

  • CPU密集型(cpu-bound):这种工作下,线程永远不会被置换到等待(waiting)状态。这种一般是进行持续性的cpu计算工作。比如计算Pi这种的就是cpu密集型工作。
  • IO密集型(io-bound):这种工作会让线程进入到等待(waiting)状态。这种情况线程会持续的请求资源(比如网络资源)或者是对操作系统进行系统调用。线程需要访问数据库的情况就是IO密集型工作。同步事件(例如mutexes、atomic)。

参考链接:理解golang调度之一 :操作系统调度 - 掘金

7、线程有继承关系吗?——没有,只有组合。

package main

import(
	"fmt"
)

type Human struct{
	name string
}

func(h Human)Dowork(){
	fmt.Println(h.name + "work")
}

func(h Human)Happy(){
	fmt.Println(h.name + "happy")
}

type Father interface{
	Dowork()
}

type Son interface{
	Father
	Happy()
}

func main(){
	human1 := Human{name:"张三丰"}
	human2 := Human{name:"张三"}

	father := human1
	son := human2
	father.Dowork()
	son.Happy()
	son.Dowork()
}

8、什么是token认证方式?

在web应用的开发过程中,我们往往还会使用另外一种认证方式进行身份验证,那就是:Token认证。基于Token的身份验证是无状态,不需要将用户信息服务存在服务器或者session中。

基于Token认证的身份验证主要过程是:客户端在发送请求前,首先向服务器发起请求,服务器返回一个生成的token给客户端。客户端将token保存下来,用于后续每次请求时,携带着token参数。服务端在进行处理请求之前,会首先对token进行验证,只有token验证成功了,才会处理并返回相关的数据。

客户端的token存在cookies里。

9、go语言反射原理?

Go反射的实现和interface和unsafe.Pointer密切相关。

先看interface的底层实现:Go的interface是由两种类型来实现的: ifaceeface

无函数的eface结构:一共有两个属性构成,一个是类型信息 _type,一个是数据信息。其中, _type可以认为是Go语言中所有类型的公共描述,Go语言中几乎所有的数据结构都可以抽象成 _type,是所有类型的表现,可以说是万能类型, data是指向具体数据的指针。

有函数的iface结构:itab是确定唯一的包含方法的interface的具体结构类型,data是指向具体方法集的指针。

Go语言中,每个变量都有唯一个静态类型(static interface type),这个类型是编译阶段就可以确定的。有的变量可能除了静态类型之外,还会有动态混合类型(dynamic concrete type)。

Go的反射法则:

  • 给定一个数据对象,可以将数据对象转化为反射对象Type和Value。
  • 给定的反射对象,可以转化为某种类型的数据对象。即法则一的逆向。
  • 通过反射对象,可以修改原数据中的内容。

Go的反射原理:在运行时,能够动态知道给定数据对象的类型和结构,并有机会修改它!

10、select有哪些特性?

select是Go语言中的一个控制语句,只用来操作的channel的读写操作(I/O操作)。

备注:golang 的 select 本质上,就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。也是常用的多路复用的一种,例如poll, epoll。

select 的特性:

  1. 如果只有一个 case 语句评估通过,那么就执行这个case里的语句
  2. 如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个
  3. 如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句
  4. 如果没有 default,那么 代码块会被阻塞,直到有一个 case 通过评估;否则一直阻塞

应用场景:

  1. 实现非阻塞读写操作。——如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句
  2. 为请求设置超时时间。
  3. 调度协程,控制其他协程的退出或者完成。

底层实现是创建select —> 注册case —> 执行case —> 释放select。结构体select如下(runtime/select.go),

type hselect struct {
    tcase     uint16   // total count of scase[]      总的case数目
    ncase     uint16   // currently filled scase[]    目前已经注册的case数目     
    pollorder *uint16  // case poll order             【超重要】 轮询的case序号
    lockorder *uint16  // channel lock order          【超重要】chan的锁定顺序
    // case 数组,为了节省一个指针的 8 个字节搞成这样的结构
    // 实际上要访问后面的值,还是需要进行指针移动
    // 指针移动使用 runtime 内部的 add 函数
    scase     [1]scase // one per case (in order of appearance)  【超重要】保存当前case操作的chan (按照轮询顺序)
}

/**
select 中每一个case的定义
*/
type scase struct {
	elem        unsafe.Pointer // data element                           数据指针
	c           *hchan         // chan                                   当前case所对应的chan引用
	pc          uintptr        // return pc (for race detector / msan)   和汇编中的pc同义,表示 程序计数器,用于指示当前将要执行的下一条机器指令的内存地址
	kind        uint16         // 通道的类型
	receivedp   *bool // pointer to received bool, if any
	releasetime int64
}

参考链接:【我的架构师之路】- golang源码分析之select的底层实现_GavinXujiacan的博客-CSDN博客

select和poll、epoll区别:

  • select:select具有O(n)的无差别轮询复杂度。
  • poll:本质上和select没有区别。它将用户传入的数组拷贝到内核空间,然后查询每个fd(file descriptor,文件描述符是内核为了高效管理已被打开的文件所创建的索引)对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。
  • epoll:epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

参考链接:select、poll、epoll之间的区别(搜狗面试) - aspirant - 博客园 - aspirant - 博客园")

11、互斥体和饥饿问题?

参考链接:https://www.jianshu.com/p/9f4376fbbe5c

12、多核情况下如何保证Cache的数据一致性?

MESI缓存一致性协议,每个缓存行都用2个bit表示四种状态,修改状态Modified,独占状态Exclusive,共享状态Shared,失效状态Invalid。

  • 修改状态:代表该缓存行中的内容被修改了,并且该缓存行只被缓存在该CPU中。这个状态的缓存行中的数据和内存中的不一样,在未来的某个时刻它会被写入到内存中。
  • 独占状态:缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。
  • 共享状态:缓存行中的数据不止存在本地CPU缓存中,还存在别的CPU的缓存中。这个状态的数据和内存中的数据是一致的。当有一个CPU修改该缓存行对应的内存的内容时会使该缓存行变成 失效状态。
  • 失效状态:该缓存行中的内容时无效的。

比如说,当CPU的某个内核Core加载某一个数据到Cache1里时,这个缓存行的状态就是独占状态,然后内核对数据做了修改,这里缓存行的状态就是修改状态。如果有另外一个内核读相同的数据时,Cache1监测到另外一个内核读取了数据,这时候缓存行的状态就变成了共享状态。在共享状态下,如果自己内核对数据做修改,那自己的缓存行就变成修改状态,其他所有缓存行变成失效状态。

13、实现一个dog、cat、fish循环输出程序。三个通道,利用dogCh->catCh->fishCh->dogCh->…->fishch控制顺序,WaitGroup等待这组的三个线程结束。

package main

import(
	"fmt"
	"sync"
	"sync/atomic"
)

const num = 10

func main(){
	var wg sync.WaitGroup
	var dogCount uint64
	var catCount uint64
	var fishCount uint64

	dogCh := make(chan struct{}, 1)
	catCh := make(chan struct{}, 1)
	fishCh := make(chan struct{}, 1)

	wg.Add(3)
	go dog(&wg, dogCount, dogCh, catCh)
	go cat(&wg, catCount, catCh, fishCh)
	go fish(&wg, fishCount, fishCh, dogCh)
	dogCh <- struct{}{}

	wg.Wait()
}

func dog(wg *sync.WaitGroup, count uint64, dogCh chan struct{}, catCh chan struct{}){
	for{
		if count>=uint64(num) {
			wg.Done()
			return
		}
		<- dogCh


![img](https://img-blog.csdnimg.cn/img_convert/8d76ad65574e630beff2e731cde4c0af.png)
![img](https://img-blog.csdnimg.cn/img_convert/28ccd4be9bcc2bd158450af377bc58af.png)
![img](https://img-blog.csdnimg.cn/img_convert/21ac3cf89067c4e64d152aec73b99155.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

		<- dogCh


[外链图片转存中...(img-r7HHCdE3-1715875887774)]
[外链图片转存中...(img-XYmSqoFJ-1715875887774)]
[外链图片转存中...(img-52BKXOd3-1715875887775)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值