写点什么

来看看现在 go 开发岗 10k 的面试强度

作者:王中阳Go
  • 2025-03-26
    北京
  • 本文字数:3784 字

    阅读完需:约 12 分钟

来看看现在go开发岗10k的面试强度

今天继续分享热乎乎的面经,来自一家中小厂的 Go 后端开发岗,薪资是 10k 左右,因为面试问到的问题我觉得都挺有代表性的,所以就想分享出来给大家学习学习:

面经详解

数组和切片的区别

回答:


数组和切片虽然都是存储同类型元素的集合,但核心区别主要体现在以下几个方面:


  1. 定义与类型

  2. 数组是固定长度的数据结构,声明时必须指定长度,例如 var arr [5]int,其类型包含长度信息(如 [3]int[5]int 是不同类型)。

  3. 切片是动态长度的,基于底层数组封装,类型仅包含元素类型(如 []int),无需指定长度。切片的结构包含指针(指向底层数组)、长度和容量三个字段。

  4. 内存分配与传递

  5. 数组是值类型,赋值或传参时会发生完整复制,适合需要强隔离的场景(如加密密钥存储)。

  6. 切片是引用类型,传递时复制的是切片头(浅拷贝),共享底层数组。修改切片元素会直接影响底层数组。

  7. 扩容与性能

  8. 数组长度固定,无法扩容;切片支持自动扩容(通过append),当容量不足时会创建新的底层数组,并将旧数据复制过去,可能触发性能开销。

  9. 切片由于动态特性,在高频修改或变长数据场景更灵活,但部分操作(如随机访问)可能略慢于数组。

  10. 初始化与使用

  11. 数组初始化需指定长度和元素值(如 arr := [3]int{1,2,3});切片可通过字面量(slice := []int{1,2,3})或从数组/现有切片创建(如 slice := arr[1:4])。



Go 的 GMP 模型

回答:


GMP 是 Go 语言并发调度的核心模型,由三个核心组件构成:


  1. G(Goroutine)

  2. 轻量级协程,初始栈仅 2KB(可动态扩展),由go关键字创建。每个 G 包含执行上下文(程序计数器、栈、寄存器状态)。

  3. M(Machine)

  4. 对应操作系统线程,负责执行 CPU 指令。M 必须绑定一个 P 才能运行 G,默认上限为 10,000 个(可通过debug.SetMaxThreads调整)。

  5. P(Processor)

  6. 虚拟处理器,管理本地 G 队列(Local Queue)和调度上下文。数量由GOMAXPROCS控制(通常等于 CPU 核心数)。


调度策略与优化:


  • Work-Stealing(工作窃取):当 P 的本地队列为空时,会从全局队列或其他 P 窃取 G,避免资源闲置。

  • Hand Off 机制:若 G 阻塞(如系统调用),M 会释放 P,由其他 M 接管,减少线程阻塞对整体性能的影响。

  • 协作式调度:通过runtime.Gosched()或通道操作主动让出 CPU,结合基于信号的抢占式调度(Go 1.14+),减少长耗时 Goroutine 的“饥饿”问题。


优势:GMP 通过细粒度调度和并发执行,实现高吞吐、低延迟的并发处理,特别适合 I/O 密集型和高并发场景(如 Web 服务器)。



defer 关键字的作用以及执行顺序

回答:


defer关键字用于延迟执行函数,常用于资源释放(如文件关闭、锁解锁)和错误处理,其核心特性如下:


  1. 执行顺序

  2. 多个defer按**后进先出(LIFO)**顺序执行。例如:


  defer fmt.Println("First")    defer fmt.Println("Second")    // 输出顺序为 Second → First  
复制代码


这种设计确保依赖资源按逆序释放(如先打开的文件后关闭)。


  1. 参数预计算

  2. defer的函数参数在声明时立即求值,而非执行时。例如:


   x := 10     defer fmt.Println(x)  // 输出10     x = 20  
复制代码


此时x的值在defer声明时已确定为 10。


  1. 常见用途

  2. = 资源管理:确保文件、网络连接等资源在函数退出时释放:


   file, _ := os.Open("file.txt")     defer file.Close()  
复制代码


错误恢复:结合recoverdefer中捕获 panic:


   defer func() {         if r := recover(); r != nil {             log.Println("Recovered:", r)         }     }()  
复制代码


日志跟踪:记录函数进入和退出时间,用于性能分析。


  1. 性能注意事项

  2. defer会引入微小性能开销(纳秒级),但在大多数场景可忽略。避免在循环中滥用defer(可改用显式函数调用)。



有接触过什么 Golang 中的设计模式吗

回答:


  1. 单例模式(Singleton)

  2. 通过sync.Once确保全局唯一实例:


   var instance *Singleton     var once sync.Once     func GetInstance() *Singleton {         once.Do(func() { instance = &Singleton{} })         return instance     }  
复制代码


适用于配置管理、日志器等场景。


  1. 工厂模式(Factory)

  2. 根据输入类型返回不同对象:


   type Vehicle interface { Drive() }     func CreateVehicle(t string) Vehicle {         switch t {         case "car": return &Car{}         case "bus": return &Bus{}         }     }  
复制代码


隐藏对象创建细节,便于扩展新类型。


  1. 策略模式(Strategy)

  2. 定义算法族并使其可互换:


   type PaymentStrategy interface { Pay(amount float64) }     type WeChatPay struct{}     type AliPay struct{}     // 上下文根据策略执行支付  
复制代码


适用于支付方式、排序算法等灵活切换的场景。


  1. 观察者模式(Observer)

  2. 通过事件通知实现松耦合:


   type Subject struct { observers []Observer }     func (s *Subject) Notify(msg string) {         for _, o := range s.observers { o.Update(msg) }     }  
复制代码


用于消息推送、状态变更通知。


  1. 装饰器模式(Decorator)

  2. 动态扩展对象功能:


   type Component interface { Operation() }     type Decorator struct { component Component }     func (d *Decorator) Operation() {         d.component.Operation()         d.AddBehavior()     }  
复制代码


适用于中间件、日志增强等场景。



Go 的垃圾回收机制

回答:


Go 的垃圾回收(GC)采用并发三色标记清除算法,主要流程如下:


  1. 标记阶段

  2. 初始标记(STW):短暂暂停程序,扫描 GC Roots(全局变量、Goroutine 栈等)。

  3. 并发标记:与程序并发执行,遍历对象图,标记存活对象为黑色,引用未扫描完的为灰色,未访问的为白色。

  4. 清除阶段

  5. 回收白色对象内存,未标记对象被加入空闲链表供后续分配。


优化技术


  • 混合写屏障:结合插入屏障(标记新引用对象)和删除屏障(保留旧引用对象),允许并发标记期间减少 STW 时间。

  • 分代假设优化:虽然没有显式分代,但通过局部性原理优化扫描顺序,优先处理年轻对象。


触发条件


  • 堆内存增长达到阈值(默认 100%,由GOGC环境变量控制)。

  • 手动调用runtime.GC()或内存不足时强制触发。


性能特点


  • 低延迟:Go 1.14 后 STW 时间通常控制在 1ms 以内,适合实时系统。

  • 吞吐量:相比 Java,Go 的 GC 更轻量,但堆内存可能更大(无分代)。



Go 垃圾回收跟其他语言的有什么区别,STW 跟其他相比做了哪些优化,减少暂停时间

回答:


对比其他语言 GC 机制:



Go 的 STW 优化:


  1. 并发标记:标记阶段与用户代码并发执行,仅初始标记和重标记需要极短 STW。

  2. 混合写屏障:允许在并发标记期间修改对象引用,无需完全暂停程序。

  3. 增量回收:将 GC 工作分摊到多次小规模操作,避免单次长时间停顿。



垃圾回收机制主要针对的是栈区的还是堆区的

回答:


垃圾回收主要针对堆内存


堆区:动态分配的对象(如newmake创建)由 GC 管理,通过标记清除回收不可达对象。


栈区:栈内存由编译器自动管理(局部变量),函数返回时立即释放,不依赖 GC。


例外情况:若栈上的指针引用了堆对象(如闭包),GC 会通过可达性分析确保堆对象不被错误回收。



使用内存池的优点

回答:


内存池(Memory Pool)通过预分配和复用内存块优化性能,核心优点包括:


  1. 减少内存碎片:固定大小块分配避免频繁动态分配导致的内存碎片。

  2. 提升分配速度:直接从池中获取内存块,避免系统调用(如malloc)的开销。

  3. 降低 GC 压力:复用对象减少垃圾产生,尤其在高频创建/销毁场景(如网络请求)。

  4. 实时性保障:适用于嵌入式或实时系统,确保内存分配时间可控。


适用场景:Web 服务器连接池、游戏对象池、数据库连接管理等。



程序打开之后加载到内存,他有多少个分区

回答:


程序内存通常分为以下区域:


  1. 代码区(Text):存储可执行指令,只读。

  2. 全局/静态区(BSS+Data)

  3. BSS 段:未初始化的全局/静态变量(默认为零值)。

  4. Data 段:已初始化的全局/静态变量。

  5. 堆区(Heap):动态分配内存(如mallocnew),由 GC 或手动管理。

  6. 栈区(Stack):存储局部变量、函数参数,自动分配释放。

  7. 常量区:存储字符串常量等只读数据(可能合并到 Text 或 Data 段)。



TCP 跟 UDP 的区别

回答:




用 TCP 设计文件上传功能,能够知道实时进度,你会怎么设计?

回答:


设计方案:


  1. 分片传输

  2. 将文件拆分为固定大小块(如 1MB),每块编号后通过 TCP 发送。

  3. 接收方按编号重组文件,确保顺序。

  4. 进度反馈

  5. 接收方每收到一个分片,返回 ACK 包含已接收的分片编号和总大小。

  6. 发送方根据 ACK 计算进度(已发送分片数/总分片数)。

  7. 断点续传

  8. 记录已传输的分片到数据库或文件,中断后从断点继续发送。

  9. 流量控制

  10. 滑动窗口机制动态调整发送速率,避免网络拥塞。


优化点


  • 多线程分片上传提升速度。

  • 压缩分片减少传输量。

  • 客户端本地计算分片哈希,服务端校验完整性。



如何设计缓存,解决缓存和数据库一致性问题

回答:


核心策略:


  1. 缓存更新策略

  2. 写穿透(Write Through):先更新数据库,再更新缓存。

  3. 延迟双删:更新数据库后,先删缓存,延迟再删一次(防旧数据回填)。

  4. 失效机制

  5. • 设置合理 TTL,结合惰性删除(访问时检查过期)。

  6. • 主动刷新热点数据,避免批量失效(缓存雪崩)。

  7. 并发控制

  8. • 使用分布式锁(如 Redis 锁),防止缓存击穿(大量请求同时查 DB)。

  9. 最终一致性

  10. • 通过消息队列异步更新缓存(如数据库变更后发送 MQ)。


容错设计


  • 降级策略:缓存失效时直接返回默认值或限流。

  • 监控告警:缓存命中率、延迟异常时触发告警。



欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。


没准能让你能刷到自己意向公司的最新面试题呢。


感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:infoq 面试群。

发布于: 4 小时前阅读数: 105
用户头像

王中阳Go

关注

靠敲代码在北京买房的程序员 2022-10-09 加入

【微信】wangzhongyang1993【公众号】程序员升职加薪之旅【成就】InfoQ专家博主👍掘金签约作者👍B站&掘金&CSDN&思否等全平台账号:王中阳Go

评论 (1 条评论)

发布
用户头像
你觉得这个难度怎么样?
4 小时前 · 北京
回复
没有更多了
来看看现在go开发岗10k的面试强度_Go_王中阳Go_InfoQ写作社区