go面试考察goroutine与channel的核心是runtime调度、安全及泄漏防范:goroutine过多会因调度器与内存压力导致假死;channel关闭后可读不可写,写则panic,读关闭channel返回零值和false,但不可依赖零值判断关闭状态。

Go 面试里问 goroutine 和 channel,不是考你会不会写 go func() 或 make(chan int),而是看你是否清楚它们在 runtime 里怎么被调度、怎么保安全、怎么不泄漏。
goroutine 创建太多会卡死吗?
会,但不是因为“线程爆了”,而是调度器和内存扛不住。G 的初始栈只有 2KB,创建快,但每个 G 仍要占内存、进队列、被 sysmon 监控。当 goroutine 数量远超 P 数(默认等于 CPU 核数)且大量处于阻塞或休眠状态时,M 频繁切换、全局队列积压、GC 扫描压力上升,整体响应就会变慢甚至假死。
- 常见诱因:循环中无限制启动 goroutine(比如每请求都
go handle(r)),又没用sync.WaitGroup或context.WithTimeout控制生命周期 - 真正瓶颈往往不在 G 本身,而在它调用的阻塞操作——比如未设超时的 HTTP 请求、没加锁的共享 map 写入、channel 发送端永远没人接收
-
GOMAXPROCS调高不能解决泄漏,只影响 P 数;runtime.NumGoroutine()是排查的第一手指标,但得结合 pprof 的 goroutine profile 看分布
channel 关闭后还能读写吗?
关闭后的 channel 允许读,不允许写。读已关闭的 channel 会立即返回零值 + false(第二个返回值);写则直接 panic:send on closed channel。
- 别依赖“读到零值”判断 channel 是否关闭——缓冲 channel 关闭前就可能读出零值(比如存的就是
0);正确方式是用val, ok := 中的 <code>ok - 多个 goroutine 同时关同一个 channel 会 panic,必须确保只关一次;常用模式是用
sync.Once包一层,或由发送方统一关闭 - 无缓冲 channel 关闭后,若还有 goroutine 在等接收,它们会立刻被唤醒并读到零值;但若有 goroutine 正在发送,就会触发 panic —— 所以关闭时机必须早于最后发送完成
select default 分支会导致忙等吗?
会,而且非常隐蔽。如果 select 里所有 channel 都不可读/不可写,而你写了 default,那这段逻辑就会变成纯用户态空转,CPU 占用飙升。
- 典型反模式:
for { select { case —— sleep 时间太短,仍可能高频轮询 - 更稳妥的做法是去掉
default,改用带超时的case ,或者把逻辑移到单独 goroutine 中用 channel 驱动 - 注意:
select本身不保证公平性,多个可执行 case 会随机选一个;不要假设顺序,尤其在有default时,它总能“插队”成功
map 并发读写为什么 panic?
Go 的 map 不是并发安全的,运行时会在检测到同时有 goroutine 写 + 任意 goroutine 读/写时,直接 throw fatal error: concurrent map read and map write。
- 这个 panic 是 runtime 主动触发的,不是竞态条件导致数据错乱后再崩——它宁可提前崩,也不让你拿到脏数据
-
sync.Map适合读多写少场景,但要注意它的LoadOrStore、Range等方法语义和原生 map 不同;高频写场景不如直接用sync.RWMutex包一层普通 map - 逃逸分析常被忽略:函数内新建的 map 若被返回或传给 goroutine,大概率逃逸到堆,增加 GC 压力;用
go tool compile -gcflags="-m"确认
真正难的不是记住这些规则,而是在写业务逻辑时下意识避开坑——比如看到循环启动 goroutine,先想“谁负责回收”;看到 channel 操作,先问“关没关、谁关、什么时候关”;看到 map,条件反射加锁或换结构。这些不是靠背题练出来的,是线上被 kill 过几次之后长出来的肌肉记忆。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











