1.进程、线程、goroutine 三者的区别与联系?从:资源占用、调度、开销、并发能力这几个方面对比。
-
进程
- 系统资源分配的最小单位,有独立内存空间。
- 开销最大,调度切换成本高。
- 量级:GB 级别资源。
-
线程
- 进程内的执行单元,共享进程资源。
- 由操作系统内核调度,栈默认 1~8MB。
- 切换成本较高,不宜过多。
-
goroutine(Go 协程)
- Go 运行时管理的用户态轻量级协程。
- 初始栈仅 2KB,动态扩缩容。
- 调度切换在用户态完成,成本极低。
- 单机轻松十万、百万级并发。
一句话总结:
进程是资源容器,线程是内核执行单元,goroutine 是 Go 实现的超高并发轻量级执行单元。
2.请你解释一下 Go 里的 GMP 模型是什么?
G、M、P 分别代表什么,它们之间是怎么协作的?
G、M、P 分别是什么
- G (Goroutine):用户任务,就是你写的
go func(),存栈、指令、状态。
- M (OS Thread):内核线程,真正被操作系统调度执行。
- P (Processor):逻辑处理器,持有 G 队列、调度上下文,用来把 G 绑定到 M 上运行。
协作流程
- 每个 P 维护一个本地 G 队列。
- M 必须绑定 P 才能运行 G。
- 一个 M 同一时间只跑一个 G。
- G 遇到阻塞(IO、channel、锁)时,M 会解绑 G,去拿 P 里的下一个 G 运行。
- P 本地队列为空时,会从全局 G 队列或其他 P 偷 G(work stealing)。
一句话满分版
G 是 goroutine 任务,M 是操作系统线程,P 是逻辑处理器。
M 必须绑定 P 才能执行 G,P 管理 G 队列,Go 调度器通过 GMP 实现 M:N 调度,支持高并发。
3.Go 里 make 和 new 的区别是什么?
New 用于任意类型,返回返回指向零值内存的指针。 make 仅用于 slice、 map、 channel,返回已初始化的值本身
4.Go 里面 slice 的底层结构是什么?slice 扩容机制大概是怎样的?
slice 底层是一个结构体,包含指针 ptr、长度 len、容量 cap。
扩容机制:
- 原容量 < 1024 时,新容量 = 旧容量 × 2;
- 原容量 ≥ 1024 时,新容量 = 旧容量 × 1.25;
- 再根据元素大小、内存对齐做向上取整。
5.Go 里 map 的底层结构是什么?为什么遍历 map 是无序的?
Map 的底层结构是个哈希表。当键值被添加到 map 中时,键会通过哈希函数计算出一个哈希值,这个哈希值决定了该键值对儿在底层数据桶中的存储位置。 Go 语言为了防止拒绝服务 DOS 攻击引入了随机化哈希函数。这意味着每次程序运行时,或者在某些情况下,即使微小的代码或者环境变化,哈希函数计算出来哈希值序序列也可能有略有不同,导致键值在哈希表上的存储顺序发生变化。
6.Go 里面 channel 有什么用?有缓冲 channel 和无缓冲 channel 的区别是什么?
channel 作用
- 用于 goroutine 之间的通信;
- 实现 同步 和 并发控制;
- 遵循 Go 理念:不要通过共享内存来通信,要通过通信来共享内存。
有缓冲 vs 无缓冲
- 无缓冲 channel
- 有缓冲 channel
- 缓冲区未满前,发送不阻塞;
- 缓冲区未空前,接收不阻塞;
- 相当于异步。
- 7.Go 里 panic 和 recover 怎么用? 什么场景下应该用,什么场景不应该用?
panic
- 主动抛出异常,中断当前执行流程。
- 会触发defer,然后程序崩溃退出。
recover
- 只能在defer 函数里使用,用来捕获 panic,恢复正常执行。
- 捕获后程序不会崩溃。
使用场景
- 应该用:真正的、不可恢复的程序错误(如程序逻辑 BUG、资源严重不足)。
- 不应该用:业务错误(如参数不合法、查询不到数据),业务错误应该用
error。
8.Go 中 interface 空接口和非空接口有什么区别?interface{} 可以存什么?
空接口 interface{}
- 没有定义任何方法
- 所有类型都默认实现空接口
- 可以存任意类型的值,类似泛型容器
非空接口(有方法的 interface)
- 定义了一组方法列表
- 只有实现了所有方法的类型,才算是实现了该接口
- 用于约束行为、多态、解耦
底层结构区别(面试加分)
- 空接口底层:
eface,只存 类型 + 数据指针
- 非空接口底层:
iface,存 接口信息 + 类型 + 数据指针
第 9 题Go 中 defer 的执行顺序是什么?defer 在什么时机执行?有哪些注意事项?
1. 执行顺序
后进先出(栈),越晚声明的 defer 越早执行。
2. 执行时机
在函数 return 之后、真正返回调用方之前执行。
3. 重要注意事项(必背)
- defer 中参数是预计算的,不是执行时才计算
- defer 可以捕获 panic,配合 recover 使用
- 循环里慎用 defer,容易延迟释放资源或堆积
- 有名返回值 + defer 配合时,可以修改返回值
10.Go 怎么实现并发安全?常用方案有哪些?
Go 实现并发安全的常用方案
-
互斥锁 sync.Mutex
独占锁,同一时间只有一个 goroutine 能进入临界区。
-
读写锁 sync.RWMutex
- 读锁:可共享,多个 goroutine 同时读
- 写锁:独占
适合读多写少场景。
-
channel 通道
通过通信来共享内存,Go 推荐的并发安全方式。
-
原子操作 sync/atomic
针对数值、指针的简单原子操作,性能最高。
-
sync.Once
保证某段代码只执行一次,常用于单例初始化。
-
sync.WaitGroup
等待一组 goroutine 全部执行完成。