golang 面试常考题
Go 语言面试中,考察点通常围绕其核心特性(并发、语法设计、内存管理等)、实战经验及底层原理。以下是高频考点及解析,按类别整理:
一、基础语法与特性
1. Go 语言的核心特性是什么?与其他语言(如 Java、Python)相比有何优势?
核心特性:
- 静态类型,语法简洁(少关键字、无继承、无异常);
- 原生支持并发(goroutine + channel);
- 自动垃圾回收(GC);
- 接口隐式实现;
- 内置工具链(
go fmt
/test
/mod
等)。
优势:
- 并发模型轻量高效(goroutine 比线程成本低,支持高并发);
- 编译速度快;
- 工程化友好(强制代码规范、依赖管理清晰);
- 适合云原生、系统编程等场景。
2. 变量声明的两种方式(var
和短变量 :=
)有何区别?
var a int
:可在包级或函数内声明,支持类型显式指定,未初始化时取零值(如int
为 0)。a := 10
:仅能在函数内使用,必须初始化(类型由右值推导),声明同时赋值。
注意:短变量声明可重复声明(需至少有一个新变量),如 a, b := 1, 2
后可 a, c := 3, 4
(a
重复,c
新变量)。
3. array
与 slice
的区别?slice
的底层结构是什么?
- array:固定长度(
[5]int
),值类型(赋值时拷贝整个数组)。 - slice:动态长度(
[]int
),引用类型(底层指向数组),更常用。
slice 底层结构(reflect.SliceHeader
):
type SliceHeader struct {Data uintptr // 指向底层数组的指针Len int // 长度(当前元素数)Cap int // 容量(最大可容纳元素数,>= Len)
}
4. slice
的扩容机制?
当 len == cap
时,append 会触发扩容:
- 若新容量 < 1024,扩容为原容量的 2 倍;
- 若新容量 >= 1024,扩容为原容量的 1.25 倍;
- 最终容量需满足
>= 新 len
(若一次 append 多个元素,可能直接扩到足够大)。
注意:扩容后会创建新数组,原 slice 仍指向旧数组(可能导致“内存泄漏”)。
5. map
的特性?为什么并发读写 map
会 panic?
特性:
- 无序键值对,键必须可比较(不能是
slice
、map
、func
); - 引用类型,必须初始化(
make(map[K]V)
)后使用。
并发问题:
map 不是并发安全的,多个 goroutine 同时读写会触发 fatal error: concurrent map read and map write
。
解决方案:用 sync.Map
(适合读多写少)或加锁(sync.Mutex
)。
二、并发编程(核心考点)
1. goroutine
与 OS 线程的区别?
- 调度:goroutine 由 Go runtime 调度(用户态),线程由 OS 调度(内核态)。
- 成本:goroutine 初始栈大小(2KB)远小于线程(通常 1MB),创建销毁成本低,支持十万级并发。
- 切换:goroutine 切换无需陷入内核,成本远低于线程切换。
2. 什么是 GPM 模型?
Go 调度器的核心模型,三者关系:
- G(Goroutine):待执行的 goroutine(包含栈、状态等)。
- P(Processor):逻辑处理器(关联一个 OS 线程),负责调度 G,维护本地可运行 G 队列。
- M(Machine):OS 线程,绑定 P 后执行 G。
工作流程:P 从本地队列或全局队列取 G 交给 M 执行,当 G 阻塞(如 IO),M 会解绑 P 并挂起,其他 M 可绑定该 P 继续执行。
3. channel
的类型及使用场景?
类型:
- 无缓冲 channel(
make(chan int)
):发送和接收需同步(一方阻塞直到另一方操作)。 - 有缓冲 channel(
make(chan int, 5)
):缓冲满时发送阻塞,缓冲空时接收阻塞。 - 单向 channel(
chan<- int
只发,<-chan int
只收):用于限制 channel 操作。
场景:
- goroutine 间通信(传递数据);
- 同步(如无缓冲 channel 实现“等待-通知”);
- 限制并发数(有缓冲 channel 作为“信号量”)。
4. 关闭 channel
有哪些注意事项?
- 重复关闭会 panic;
- 向已关闭的 channel 发送数据会 panic;
- 从已关闭的 channel 接收数据:先取完缓冲数据,再返回零值(可通过多返回值判断:
val, ok := <-ch
,ok
为 false 表示已关闭)。
5. select
语句的作用?如何避免 select
中的死锁?
作用:同时监听多个 channel 的读写操作,执行第一个就绪的 case;若所有 case 阻塞,且有 default
则执行 default,否则阻塞。
避免死锁:
- 确保至少有一个 case 可就绪(或加
default
防止永久