Go 语言知识手册

深度整理 · 覆盖语言核心、并发模型、内存 GC、接口反射、新版本特性与工程化实践

1. 基础与语言特性

1.1 值类型 vs 引用类型

Go 中没有"引用传递",一切传参都是按值传递。所谓"引用类型"(slice、map、channel、func、interface)只是其内部数据结构的指针被复制了。

func main() {
    s := []int{1,2,3}
    modify(s)      // 传的是 slice 头(指针+len+cap)的副本
    fmt.Println(s) // [100 2 3] —— 底层数组被改
}
func modify(s []int){ s[0]=100 }
类型传参行为底层结构
int/string/struct整体复制(拷贝值)内存本体
slice复制 slice 头(ptr/len/cap)ptr 指向底层数组
map复制 hmap 指针*hmap
channel复制 hchan 指针*hchan

1.2 指针与 nil

Go 指针不能进行算术运算(无向后兼容的指针偏移)。nil 指针、nil 接口、nil map、nil slice 的判等是高频考点。

陷阱:nil 接口 ≠ nil 指针。一个被赋了"值为 nil 的指针"的接口,其 iface 的 data 是 nil,但 tab 不为 nil,所以接口本身 != nil。

1.3 new vs make

关键字适用类型返回作用
new(T)任意类型*T(指针,零值填充)分配内存并清零,返回指针
make(T)slice/map/channelT(非指针)初始化内部结构(长度、容量、哈希表)

1.4 结构体内存对齐

结构体字段会按对齐规则填充 padding,字段顺序影响结构体总大小。可用 unsafe.Sizeof 验证。

type A struct{ a int8; b int64; c int8 } // 8 + 8 + 8 = 24(有 padding)
type B struct{ b int64; a int8; c int8 } // 8 + 1 + 1 + padding = 16

1.5 错误处理

Go 1.13 起支持 %w 错误包装及 errors.Is/As。

if errors.Is(err, sql.ErrNoRows) { ... }
var e *MyError
if errors.As(err, &e) { ... }

接口约定:错误值通常放在返回值最后一位;错误只处理一次(if err != nil);包装用 %w,格式化用 %v。

1.6 panic / recover

recover 只能在 defer 中生效。panic 时先执行所有 defer(栈式),再逐级向上传播直至被 recover 或程序退出。

2. 核心数据类型(底层)

2.1 slice 底层

slice 本质是一个结构体:{data unsafe.Pointer; len int; cap int}。

s := []int{1,2,3,4,5}
a := s[1:3]   // [2 3],共享底层
b := append(a, 99) // 可能覆盖 s 的索引3,也可能扩容(取决于 cap)

2.2 map 底层

map 由 hmap 指向多个 bmap(桶),桶内用哈希的高位定位槽位。冲突过多时扩容/拉链。

考点:map 的遍历顺序不保证;删除元素不立即释放内存;不能对 map 的 value 取地址(value 不可寻址)。

2.3 string

2.4 struct 与匿名字段

匿名字段实现"嵌入",用于模拟组合/继承,常搭配接口实现。

3. 并发模型(重点)

3.1 goroutine 与 GMP 调度器

goroutine 是协程,由 Go 运行时调度,初始栈约 2KB,可增长。GMP 调度器分级:

3.2 channel 原理

channel 底层是 hchan 结构(环形队列 + sendq/recvq 等待队列 + 锁)。

场景行为
无缓冲 channel同步通信,发送方必须等接收方就绪(sendq/recvq 直接对接),无缓冲区。
有缓冲 channel写满/读空时阻塞;缓冲区共享,使用锁定保证并发安全。
close发送方关闭;接收方读到零值 + ok=false;对已关闭 channel send 会 panic。
ch := make(chan int, 3)
ch <- 1          // 发送
v, ok := <-ch    // 接收 + 是否已关闭
for v := range ch { ... } // 自动处理关闭
select {
case v := <-ch: ...
default:          // 非阻塞接收
}

3.3 channel 通信 vs 共享内存

不要通过共享内存来通信,而应通过通信来共享内存。

channel 天然同步、无锁、用传递数据代替锁保护临界区。适合作为 goroutine 间的所有权转移。

3.4 sync 包

类型用途
Mutex / RWMutex互斥 / 读写锁(读多写少用 RWMutex)
WaitGroup等待一组 goroutine 完成
Once保证某操作只执行一次(单例)
Cond条件变量,wait/signal/broadcast
sync.Map读多写少无锁并发 map
atomic原子操作(Add/Load/CompareAndSwap)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(){ defer wg.Done(); work() }()
}
wg.Wait()
常见考点:锁不可重入(同一 goroutine 加锁两次会死锁);Mutex 不可复制(go vet 会警告);拷贝 sync 类型会破坏锁语义。

3.5 context

context 用于在多个 goroutine 间传递取消信号、超时、携带请求级数据。核心接口:

func ctx() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go worker(ctx)
}
// 常用: WithDeadline / WithTimeout / WithValue

约定:context 作为函数第一个参数,只用于传递,不存于结构体(用 context.Value 传递数据是例外且不推荐)。

3.6 常见并发模式

3.7 竞态检测

用 go run -race / go build -race 检测数据竞争(data race)。竞态 = 两个及以上的 goroutine 无同步访问同一内存,且至少一个写。

4. 内存管理与 GC

4.1 逃逸分析

编译器决定变量分配在栈还是堆。变量是否逃逸(在函数外可见 / 生命周期超出作用域)决定了内存分配位置。

func newSlice() []int { s := make([]int,1,1); return s } // s 逃逸到堆(返回引用)
func use() int { x := 5; return x } // x 不逃逸,在栈上

4.2 内存分配

Go 内存分配器(基于 TCMalloc):

小对象(≤ 32KB)走 mcache;大对象直接向 mheap 申请。对象按 size class 分级(如 8/16/32...B)。

4.3 GC 三色标记 + 混合写屏障

Go GC 是并发(concurrent)+ 三色标记清除 + 混合写屏障。

考察点:为什么用"并发标记"降低停顿;为什么需要混合写屏障(防止漏标导致对象被提前回收/堆栈不一致)。

4.4 内存泄露常见场景

5. 接口与反射

5.1 interface 底层

接口在运行时有两种底层结构:

接口实现是隐式的:类型只要实现方法集即可赋值给接口,无需 implements 声明。

5.2 类型断言

var v interface{} = "hello"
s, ok := v.(string)   // ok=true
// 或 switch
switch x := v.(type) {
case string: ...
case int: ...
}

5.3 反射 reflect

通过 reflect.TypeOf / reflect.ValueOf 获取类型与值,用于通用序列化、ORM、依赖注入、编写泛型工具。反射性能差(运行时动态),通常仅在框架层使用。

6. 新版本特性(加分项)

6.1 Go 1.18:泛型(Generics)

func Max[T ~int | ~float64](a, b T) T { if a > b { return a }; return b }
func Map[T, U any](s []T, f func(T) U) []U { ... }
type Number interface{ ~int | ~float64 }
var _ T = Max(1, 2)

核心:类型参数 + 约束(constraint,用 any 或自定义接口表示),支持 ~int 底层类型。

6.2 Go 1.21:min/max/clear、标准库增强

新增 min/max/clear 内建函数;新增 slices、maps、cmp 包,泛型操作更简洁。

clear(m)       // 清空 map
clear(s)       // 清空 slice(置零)
min(1,2,3) // 1
s := slices.Clone(s) // 克隆

6.3 Go 1.22:循环变量语义修正 + ServeMux

重大改变:Go 1.22 起 for i, v := range 的循环变量每次迭代都是新变量,修复了经典闭包捕获错误。旧代码需用 go 1.22 指令控制。

// Go 1.22 之前:全部打印 3(捕获同一变量)
// Go 1.22 之后:依次打印 0 1 2(每次新变量)
for i := 0; i < 3; i++ {
    go func(){ fmt.Println(i) }()
}

同时 net/http 的 ServeMux 支持更灵活的路径方法匹配(如 GET /path)。

6.4 Go 1.23:迭代器与 range-over-func

新增 iter 包、range 支持自定义 func(func(K,V)bool) 迭代器,可对任意集合做 for-range。

6.5 Go 1.24(近期):泛型别名等

泛型类型别名(Generic type aliases)逐步落地;标准库持续加强。具体以官方 release notes 为准。

7. 工程化与实践

7.1 模块与依赖

使用 Go Modules(go.mod)管理依赖;go mod tidy 整理;版本选择遵循 MVS(最小版本选择)。

7.2 接口设计原则

7.3 性能优化与 pprof

net/http/pprof 提供 CPU、内存、goroutine、锁分析;go test -bench 做基准测试;go tool pprof 分析燃烧。

7.4 错误处理惯例

用 fmt.Errorf("...: %w", err) 包装并保留错误链;用 errors.Is/As 判定;库代码避免 panic,返回 error。

7.5 常见设计模式

8. 速查清单

主题一句话答案
slice 扩容cap<256 翻倍,≥256 按 1.25 倍左右渐进
map 非并发安全并发读写需加锁或 sync.Map
GMPG 任务、M 线程、P 处理器 + 工作窃取
channel 无缓冲同步,发送接收直接对接
GC并发三色标记 + 混合写屏障,低停顿
API 设计接受接口,返回结构体
goroutine 栈初始约 2KB,动态增长