1. GMP 调度模型
| 组件 | 角色 |
|---|---|
| G(Goroutine) | 执行体,每个 goroutine 对应一个 G |
| M(Machine) | OS 线程,G 必须绑定到 M 才能执行 |
| P(Processor) | M 的上下文,持有本地 G 队列(默认 GOMAXPROCS 个) |
work-stealing 机制:当本地 G 队列空时,M 先从全局队列取 G;全局队列也空时,从其他 P 的本地队列偷一半 G。
2. GC
Go 使用并发标记-清除(三色标记法)的 GC,关键阶段:
| 阶段 | 说明 |
|---|---|
| Mark Setup(STW) | 开启写屏障 |
| Marking(并发) | 从根对象出发,三色标记可达对象 |
| Mark Termination(STW) | 重新扫描,处理写屏障记录 |
| Sweeping(并发) | 回收白色对象 |
写屏障保证并发标记期间不会漏标新分配或被修改指针的对象。
Go 1.19+ 进一步优化了软内存限制(GOMEMLIMIT),减少 STW 频率。
3. 栈与堆
| 栈 | 堆 | |
|---|---|---|
| 分配时机 | 编译期确定大小的局部变量 | new/make 或逃逸分析判定需堆分配的变量 |
| 生命周期 | 函数返回即释放 | GC 回收 |
| 访问速度 | 快(CPU cache 友好) | 慢(需指针解引用) |
| 扩容 | Goroutine 栈动态扩展(初始 2KB) | — |
逃逸分析:编译器判断变量是否在函数返回后仍被引用。引用了 → 分配在堆,未引用 → 在栈。
4. Slice 与 Array
| Array | Slice | |
|---|---|---|
| 长度 | 固定,类型的一部分([3]int) | 动态 |
| 值/引用 | 值类型(传参会复制) | 引用类型(底层数组共享) |
| 使用频率 | 极少 | 高 |
Slice 底层结构:ptr(指针)+ len(长度)+ cap(容量)。对 Slice 切片产生的是新的 header,但底层数组共享。
扩容规则(Go 1.18+):
| |
Go 切片 vs Python 切片:
| Go Slice | Python Slice | |
|---|---|---|
| 底层 | 共享数组 | 创建新 list |
| 修改成员 | 影响原 Slice | 不影响原 list |
| 负数索引 | 不支持 | 支持 |
[a🅱️c] | c 是容量 | c 是步长 |
5. Channel 使用注意事项
| 操作 | nil channel | 已关闭 channel | 正常 channel |
|---|---|---|---|
读 <-ch | 永久阻塞 | 返回零值(可读尽) | 阻塞或读到值 |
写 ch <- | 永久阻塞 | panic | 阻塞或写入 |
关闭 close(ch) | panic | panic | 成功 |
关键规则:
- 不能向已关闭的 channel 写数据(panic)
- 不能关闭 nil channel
- 不能重复关闭 channel
- 无缓冲 channel:读写同步,写等读、读等写
- 有缓冲 channel:缓冲区未满时写不阻塞,缓冲区有数据时读不阻塞
- 只有发送方应关闭 channel(接收方关闭可能 panic)
6. Mutex 注意事项
| 规则 | 说明 |
|---|---|
| Go 的 Mutex 不可重入 | 同一 goroutine Lock 两次 → 死锁 |
| 谁 Lock 谁 Unlock | goroutine A Lock,goroutine B Unlock → 可编译通过但逻辑错误 |
RWMutex 额外规则:读锁之间不互斥,写锁与读写都互斥。不可升级(持有读锁时再 Lock 写锁 → 死锁)。
7. 值类型与引用类型
| 值类型 | 引用类型 |
|---|---|
| int, float, bool, string | slice, map, channel, pointer, interface |
| struct(字段全为值类型时) | func |
| array |
string 不可变(immutable),赋值和传参时复制的是 header(ptr+len),不是底层字节数组。
8. 线程安全的类型
Go 标准库中:
| 类型 | 线程安全? | 说明 |
|---|---|---|
sync.Map | 是 | 读多写少场景优于 map + Mutex |
chan | 是 | channel 操作本身是并发安全的 |
sync.Mutex / sync.RWMutex | 是 | 互斥锁本身就是线程安全机制 |
atomic.* | 是 | 原子操作 |
普通 map | 否 | 并发读写会 fatal error(非 panic,不可 recover) |
slice | 否 | 并发 append 导致数据竞争 |