Go后端面试题收集(高级特性)
2026/5/25大约 3 分钟
高级特性
uintptr与unsafe.Pointer的区别
?没用过
- unsafe.Pointer(通用指针):
- 它是桥梁。它可以指向任何类型的变量,也可以在不同类型的指针之间进行转换(如
*int转成*float64)。 - GC 友好:GC 会跟踪
unsafe.Pointer指向的内存,确保该对象不会被回收。
- 它是桥梁。它可以指向任何类型的变量,也可以在不同类型的指针之间进行转换(如
- uintptr(地址数值):
- 它就是一个整数。它表示的是内存地址的数值。
- GC 危险:GC 不认为
uintptr是一个引用。如果一个对象只有uintptr指向它,GC 会随时回收这块内存。
- 配合使用(黑魔法逻辑):
- Go 不允许指针运算(如 p++)。如果你想移动指针,必须走这条路:
unsafe.Pointer(原始指针) ->uintptr(转成数值进行加减运算) ->unsafe.Pointer(转回指针)。
- Go 不允许指针运算(如 p++)。如果你想移动指针,必须走这条路:
- 结论:
unsafe.Pointer是指针(逻辑引用),uintptr是数值(地址快照)。
reflect如何获取字段tag?为什么json包不能导出私有变量的tag
学完教程就把这个知识放一边了
私有变量在package之外不可见,而coding/json就是使用反射(而不是protobuf那种代码生成方法)获取字段类型、字段反射值的,不可见的变量自然没法反射获得
- 获取 Tag:
使用reflect.TypeOf()获取结构体的类型对象,通过Field(i)或FieldByName()得到StructField结构体,其 Tag 字段提供了Get(key string)方法。tag := reflect.TypeOf(s).Field(0).Tag.Get("json") - 私有变量(未导出字段)的限制:
- 语言规范:Go 规定首字母小写的字段是包外不可见的。
- 反射机制:虽然
reflect可以通过一些特殊的黑魔法(如CanInterface()配合unsafe)强行读取私有变量,但 encoding/json 是一个外部包,它遵循标准的 Go 可见性规则。 - 核心原因:JSON 序列化需要将字段值转换成
interface{},而 Go 不允许将一个未导出的私有字段安全地转换为接口类型,否则就破坏了封装性。
协程和线程的区别
?
- 内存消耗:
- 线程:通常固定分配 1MB~8MB 的栈空间,不仅浪费内存,也限制了并发数。
- 协程:起始仅需 2KB,且支持动态扩容(最大可达 1GB)。
- 调度模型 (GMP):
- 线程:由内核调度,涉及内核态与用户态的切换,开销大(微秒级)。
- 协程:由 Go 运行时(Runtime)在用户态调度,通过 GMP 模型将多个协程映射到少量内核线程上,切换开销极小(纳秒级)。
- 上下文切换内容:
- 线程:需要保存大量的寄存器、程序计数器、堆栈指针等。
- 协程:只需保存极少数的寄存器状态(约 3 个)。
GC的过程是怎么样的
?
Go 采用的是 无分代、并发、三色标记清除算法:
- 准备阶段:开启写屏障,此时需要短暂的 STW(Stop The World)。
- 标记阶段(核心):
- 白色:潜在的垃圾,初始状态。
- 灰色:已被 GC 扫描,但其引用的对象尚未扫描。
- 黑色:存活对象,已被完全扫描。
- 逻辑:从根对象(栈、全局变量)开始,将引用的对象从白色染灰,再由灰染黑。
- 标记终止阶段:关闭写屏障,进行简短的 STW,处理遗留的灰色对象。
- 清理阶段:并发清理掉所有剩余的白色对象。
- 黑魔法:写屏障 (Write Barrier):
为了保证在 GC 并发扫描时,用户协程修改指针不破坏一致性,Go 引入了写屏障。它会在你给指针赋值时插入一段代码,将被引用的对象强制染灰,确保“黑色对象不会指向未被染灰的白色对象”。