GO 面试题基础篇【面试官这样问】

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
13、GO学习之 数据库(Redis)
14、GO学习之 搜索引擎(ElasticSearch)
15、GO学习之 消息队列(Kafka)
16、GO学习之 远程过程调用(RPC)
17、GO学习之 goroutine的调度原理
18、GO学习之 通道(nil Channel妙用)
19、GO学习之 同步操作sync包
20、GO学习之 互斥锁、读写锁该如何取舍
21、GO学习之 条件变量 sync.Cond
22、GO学习之 单例模式 sync.Once
23、GO 面试题总结一【面试官这样问】
24、GO 面试题进阶篇【面试官这样问】

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
在学习了 go 之后,突然感觉对 go 很感兴趣,相对于写了 七八年的 JAVA 老司机来说,完全是一个很有诱惑力和新鲜感的 兰博基尼、特斯拉 或者是最新的 小米SU7(这些车都没开过)。
这不,最近又是金三银四了, gopher 们又双叒叕开启了开挂模式,疯狂刷面试题,疯狂投简历,疯狂面试。
既然如此,那面试官们平时喜欢问什么呢?面试官这样问。
^ _ ^ 哈哈哈!gopher们看过来!!! ^ v ^
其实我也不是那么的对 go 很有经验,此篇仅仅对基本的面试题小结一下。

一、基础面试题

1.1 谈谈你对go语言的理解?

针对这个面试题,首先要知道面试官最想听到的是什么?其实在我理解,你要了解 GO 语言的概念和用途,你只要说出这几个关键点基本就 OK 了。

答:
GO 是一种 静态类型编译型 的编程语言,GO 语言的 简洁性高效性 是比较突出的特点。
而且通过 垃圾回收机制 可以减轻开发者的负担,不用操心对象的销毁。
此外,GO 语言还天生支持 高并发 编程,只需要简单的用 go 关键字就可以单独启动一个协程,也特别适合分布式系统开发 。
Go语言有 丰富的标准库 强大的工具链,这些都为开发者提供了很大的便利。

这样的回答展示了你对Go语言特点、优势以及实际应用经验的了解,有助于给面试官留下良好的印象。

移步我的系列博客第一篇《GO学习之Hello World》,里面有对 GO 语言的特性进行介绍。

1.2 切片(Slice)相关面试题

下面是面试中面试官常问的关于切片的面试题,移步 《GO学习之切片操作》,里面有对切片的定义和操作的讲解。

1.2.1 请简单介绍一下什么是切片(Slice)?

答:
切片(Slice)是Go语言中一种灵活方便且动态长度的数据结构,它是对数组的一个连续片段的引用
切片提供了动态大小的支持自动扩容的序列,并且提供了一种方便且灵活的方式来操作和管理一组相同类型的元素。
切片由三部分组成:指针长度容量
指针指向数组的第一个元素,切片的长度表示当前存储元素的个数容量表示切片底层数组的长度

加分回答: 通过指定起始索引和结束索引,我们可以从数组或另一个切片中创建一个新的切片,这样做可以在不修改原始数据的情况下对数据进行切割、筛选或重组

通过上面的回答,相信面试官是比较满意的,因为你的回答表示你对切面有一个相对全面的了解,那面试官很有可能继续问你和数组的区别。

1.2.2 那请简单说说切片(Slice)和数组(Array)的区别?

要回答这个问题,首先你要知道,切片和数据的主要在于长度的固定性和内存的管理方式。

答:
切片和数组的底层数据结构是相同的,都是连续的的内存块
数组是有固定长度的数据结构,其长度在创建时就确定了,而切片的长度是动态可变的。
切片是对底层数组的引用,它不会拥有自己的数据空间,因此对切片的修改会影响到底层数组的内容。

1.2.3 什么是深拷贝?什么是浅拷贝?

要回答这个问题,你要知道他们是 指在复制数据结构时所涉及的深度和影响范围

答:
浅拷贝只复制了数据结构的最外层,而不会复制内部的引用类型所指向的数据,也就是说,浅拷贝只是复制了引用,而不是实际的数据
而深拷贝则是复制了所有,就是复制了引用和原始数据,复制后数据完全独立,互不影响

加分回答: 在 Go 开发中,对于切片的复制通常是浅拷贝,因为切边本身只是对数据的引用,复制切片只会复制引用,而不会影响底层的内容。如果需要深拷贝,就要进行遍历进行复制元素。

1.2.4 切片(Slice)的扩容机制是怎么样的?

答:
切片的库容策略是按照一定的增长因子扩容的,一般情况下是容量的 2 倍,意味着每次扩容后新的容量是原来的 2 倍。
但如果切片容量 小于1024 个元素时,每次扩容后增加原容量的 1/4,若容量 超过 1024(含1024) 时,每次扩容将会增加原容量的 1/2
切片扩容过程中,会创建一个新的底层数组,并将原始数据复制到新数组中。

1.2.5 切片(Slice)线程安全吗?Why?

答:
切片本身并 不是线程安全的 。当多个 goroutine 同时对一个切片进行读写操作是,可能会导致竞争,从而引发数据错误或者运行时程序错误和其他不确定的行为。
当进行 并发读写操作 或者是 扩容操作 时,此时如果有多个 goroutine 进行对同一个切片发生冲突,进而引发竞争条件和数据竞争的问题。

1.3 Map 相关面试

1.3.1 请问什么是 Map,以及底层实现原理是什么?

答:
Map 是Go语言中的一种数据结构,用于存储键值对的集合。
Map 的底层实现是基于哈希表实现的,哈希表是一种数据结构,通过哈希函数将键映射到存储桶(Bucket)中,然后在桶中存储键值对。
由于哈希表中存在哈希冲突,因此哈希表中的每个存储桶通常是一个链表或者红黑树,用于存储具有相同哈希值的键值对。当发生哈希冲突时,Map 会通过链表或者红黑树来处理冲突,从而保证 Map 的正确性和稳定性。

1.4 请解释一下在 Go 中如何使用 defer 语句?

1.4.1 defer 的使用场景?

答:
在 Go 中,defer 语句会将其后面跟随的函数调用推迟到调用函数即将返回之前调用。这在处理一些清理工作(例如关闭文件、解锁互斥锁等)时特别有用。

1.4.2 defer 有什么好处?

答:
defer 可以将资源释放等清理逻辑与其获取或初始化的代码分离开来,使得代码结构更加清晰、易于阅读和维护
在函数执行过程中,如果发生错误需要提前退出或返回,defer 语句可以确保之前已经执行的 defer 函数调用仍会被执行

1.5 什么是 rune 类型?

答:
ASCII 码只需要 7 bit 就可以完整地表示,但只能表示英文字母在内的128个字符,为了表示世界上大部分的文字系统,发明了 Unicode, 它是ASCII的超集,包含世界上书写系统中存在的所有字符,并为每个代码分配一个标准编号(称为Unicode CodePoint),在 Go 语言中称之为 rune,是 int32 类型的别名

二、通道(Channel)

下面是面试中面试官常问的关于通道的面试题,移步 《GO学习之 通道(Channel)》了解更多。

2.1 请简单说说什么是通道(Channel)?

答:
通道(Channel)是Go语言中用于在goroutine之间进行通信同步的一种数据结构,通道可以看做是一种先入先出(FIFO)队列
通过 ,我们可以在不同的 goroutine 之间数据传递,从而实现不同的 goroutine 直接的协作。
通道又分为有缓冲无缓存通道。

2.2 有缓冲通道和无缓存通道有啥区别?

答:
无缓存通道:无缓冲通道是同步的,保证了数据的准确性和顺序性,无缓冲通道是指在发送数据时,发送方会阻塞直到有其他 goroutine 同时准备好接收数据。
有缓冲通道:有缓冲通道是异步的,创建时指定了一个固定的缓冲区大小,发送操作会在缓冲区未满时立即返回,接收操作会在缓冲区不为空时立即返回

2.3 通道关闭后,接受操作会成功吗?会不会阻塞?

答:
当通道关闭后,尝试从通道接收值的操作不会被阻塞,而是会立即返回并返回通道的元素类型的零值
对于数值类型,零值是 0;
对于字符串,零值是空字符串 “”;
对于接口、引用类型和复合类型,则是 nil。

三、协程(Goroutine)

下面是面试官对 goroutine 常问的面试题,移步《GO学习之 协程(goroutine)》了解更多。

3.1 请简单描述一下什么是 进程、线程、协程 以及直接的关系?

答:
进程是系统级别的,是操作系统中的一个实例,操作系统分配资源的最小单位,每个程序都可以由多个进程同时执行,每个进程都在独立的地址空间执行。
线程是进程的一个执行单元,一个进程有多个线程,同一个进程内的多个线程共享相同的内存空间和系统资源,因此线程间通信更加方便,线程也是操作系统调度的最小单位,同一个进程的多个线程可以在同一地址空间执行。
协程是一种用户级的轻量线程,它由程序员控制调度,而不是操作系统,协程可以看作是一种特殊的线程,但不同于操作系统线程,协程占用资源更少。

3.2 协程有什么优势?

答:
协程是一种用户级的轻量级的特殊线程,是在用户空间进行调度,不需要操作系统上下文进行切换;
协程之间的切换由程序员自己决定,因此可减少线程切换的开销,提高程序的并发性。
在 Go 语言中,协程(goroutine)的调度是有 Go 运行时(runtime)进行管理的,而不是操作系统调度。这意味着Go 中协程的创建、销毁、切换等操作都是有Go语言运行时库自己管理,也就是 GPM 调度运行,而不需要操作系统的线程调度机制。

3.3 go 关键字调用为啥没执行?

func main() {
	fmt.Println("main函数执行!")
	go run(0)
	fmt.Println("main函数执行结束!")
}

func run(i int) {
	fmt.Printf("当前第 %+v 个线程执行!\n", i)
}

运行 main 函数,控制台打印信息是什么样的?

main函数执行!
main函数执行结束!

发现 run() 函数并没有运行,是因为 main 自身也是一个协程,而其他协程是由 main 函数创建,当 main 函数结束后,其他协程也就自动结束了,所以 run 函数没来得及运行。

3.4 请简单描述一下什么是 GMP 调度器?

答:
G:代表goroutine,存储了goroutine的执行栈信息、goroutine状态及goroutine的任务函数等。另外G对象是可以重用的。
M:M代表着真正的执行计算资源,也就是操作系统线程。在绑定有效的P后,进入一个调度循环;而调度循环的机制大致是从各种队列、P的本地运行队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到M。如此反复。M并不保留G状态,这是G可以跨M调度的基础。
P:代表逻辑processor,P的数量决定了系统内最大可并行的G的数量(前提:系统的物理CPU核数>=P的数量)。P中最有用的是其拥有的各种G对象队列、链表、一些缓存和状态。

请移步《GO学习之 goroutine的调度原理》了解更多。

四、错误处理和异常捕获(panic / recover)

答:
Go 语言中,错误处理是通过函数返回一个额外的error类型值来指示函数执行是否成功以及可能发生的错误。在调用函数时,我们通常会检查返回的error值是否为nil,如果不为nil,则表示函数执行过程中出现了错误,这种方式是Go语言中推荐的错误处理方式。
Go语言中没有异常的概念,而是使用panic和recover机制来处理运行时错误。当程序遇到严重错误时,可以使用panic函数抛出一个异常。然后,我们可以使用defer关键字配合recover函数来捕获并处理panic引发的异常,recover函数用于恢复异常并获取panic的值。

五、sync 包(互斥锁)相关面试题

5.1 对 sync 包了解多少?

答:
sync 包是 Go 语言标准库中提供的一个用于处理并发的工具包,它提供了各种同步原语和数据结构,帮助开发者编写并发安全的代码。
那么最常用的原语包括 Mutex、RWMutex、WaitGroup 等。
Mutex(互斥锁):保护临界区,确保同一时间只有一个 goroutine 可以访问共享资源;
RWMutex(读写互斥锁):允许多个goroutine同时访问资源,但是写操作是会阻塞读操作,实现读写分离的并发控制;
WaitGroup :等待同一组 goroutine 完成执行,通常用于协调多个 goroutine 的并发执行;

除了这些,还有 Once、Cond、Map 等
Once :用于执行某个函数且确保只执行一次;
Cond :用于在多个 goroutine 之间进行条件等待和通知;
Map :则是一个并发安全的映射类型,支持并发读写操作。

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值