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

wangzhongyang007 · 大约22小时之前 · 115 次点击 · 预计阅读时间 8 分钟 · 大约8小时之前 开始浏览    

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

面经详解

数组和切片的区别

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

  1. 定义与类型

    • 数组是固定长度的数据结构,声明时必须指定长度,例如 var arr [5]int,其类型包含长度信息(如 [3]int[5]int 是不同类型)。
      切片是动态长度的,基于底层数组封装,类型仅包含元素类型(如 []int),无需指定长度。切片的结构包含指针(指向底层数组)、长度和容量三个字段。
  2. 内存分配与传递

    • 数组是值类型,赋值或传参时会发生完整复制,适合需要强隔离的场景(如加密密钥存储)。
    • 切片是引用类型,传递时复制的是切片头(浅拷贝),共享底层数组。修改切片元素会直接影响底层数组。
  3. 扩容与性能

    • 数组长度固定,无法扩容;切片支持自动扩容(通过append),当容量不足时会创建新的底层数组,并将旧数据复制过去,可能触发性能开销。
    • 切片由于动态特性,在高频修改或变长数据场景更灵活,但部分操作(如随机访问)可能略慢于数组。
  4. 初始化与使用

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

Go的GMP模型

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

  1. G(Goroutine)

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

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

    • 虚拟处理器,管理本地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. 执行顺序
    • 多个defer后进先出(LIFO)顺序执行。例如:
  defer fmt.Println("First")  
  defer fmt.Println("Second")  
  // 输出顺序为 Second → First

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

  1. 参数预计算
    • defer的函数参数在声明时立即求值,而非执行时。例如:
   x := 10  
   defer fmt.Println(x)  // 输出10  
   x = 20

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

  1. 常见用途
    = 资源管理:确保文件、网络连接等资源在函数退出时释放:
   file, _ := os.Open("file.txt")  
   defer file.Close()

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

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

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

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

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

回答:

  1. 单例模式(Singleton)
    通过sync.Once确保全局唯一实例:
   var instance *Singleton  
   var once sync.Once  
   func GetInstance() *Singleton {  
       once.Do(func() { instance = &Singleton{} })  
       return instance  
   }

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

  1. 工厂模式(Factory)
    根据输入类型返回不同对象:
   type Vehicle interface { Drive() }  
   func CreateVehicle(t string) Vehicle {  
       switch t {  
       case "car": return &Car{}  
       case "bus": return &Bus{}  
       }  
   }

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

  1. 策略模式(Strategy)
    定义算法族并使其可互换:
   type PaymentStrategy interface { Pay(amount float64) }  
   type WeChatPay struct{}  
   type AliPay struct{}  
   // 上下文根据策略执行支付

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

  1. 观察者模式(Observer)
    通过事件通知实现松耦合:
   type Subject struct { observers []Observer }  
   func (s *Subject) Notify(msg string) {  
       for _, o := range s.observers { o.Update(msg) }  
   }

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

  1. 装饰器模式(Decorator)
    动态扩展对象功能:
   type Component interface { Operation() }  
   type Decorator struct { component Component }  
   func (d *Decorator) Operation() {  
       d.component.Operation()  
       d.AddBehavior()  
   }

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


Go的垃圾回收机制

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

  1. 标记阶段

    • 初始标记(STW):短暂暂停程序,扫描GC Roots(全局变量、Goroutine栈等)。
    • 并发标记:与程序并发执行,遍历对象图,标记存活对象为黑色,引用未扫描完的为灰色,未访问的为白色。
  2. 清除阶段

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

优化技术

  • 混合写屏障:结合插入屏障(标记新引用对象)和删除屏障(保留旧引用对象),允许并发标记期间减少STW时间。
  • 分代假设优化:虽然没有显式分代,但通过局部性原理优化扫描顺序,优先处理年轻对象。

触发条件

  • 堆内存增长达到阈值(默认100%,由GOGC环境变量控制)。
  • 手动调用runtime.GC()或内存不足时强制触发。

性能特点

  • 低延迟:Go 1.14后STW时间通常控制在1ms以内,适合实时系统。
  • 吞吐量:相比Java,Go的GC更轻量,但堆内存可能更大(无分代)。

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

回答:
对比其他语言GC机制:

特性 Go Java Python
算法 并发三色标记清除 分代(年轻代+老年代) 引用计数+分代标记清除
STW时间 <1ms(混合写屏障优化) 可达几十ms(CMS/G1) 依赖全局解释器锁(GIL)
内存模型 无分代,整体扫描 分代优化年轻代回收频率 分代但受GIL限制
适用场景 高并发、低延迟 大型企业应用 短期任务、脚本

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)
    • BSS段:未初始化的全局/静态变量(默认为零值)。
    • Data段:已初始化的全局/静态变量。
  3. 堆区(Heap):动态分配内存(如mallocnew),由GC或手动管理。
  4. 栈区(Stack):存储局部变量、函数参数,自动分配释放。
  5. 常量区:存储字符串常量等只读数据(可能合并到Text或Data段)。

TCP跟UDP的区别

回答:

特性 TCP UDP
连接性 面向连接(三次握手) 无连接
可靠性 可靠传输(重传、拥塞控制) 尽力交付,可能丢包
数据顺序 保证数据顺序 不保证顺序
头部开销 大(20字节) 小(8字节)
适用场景 文件传输、Web请求 视频流、实时游戏

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

回答:
设计方案:

  1. 分片传输

    • 将文件拆分为固定大小块(如1MB),每块编号后通过TCP发送。
    • 接收方按编号重组文件,确保顺序。
  2. 进度反馈

    • 接收方每收到一个分片,返回ACK包含已接收的分片编号和总大小。
    • 发送方根据ACK计算进度(已发送分片数/总分片数)。
  3. 断点续传

    • 记录已传输的分片到数据库或文件,中断后从断点继续发送。
  4. 流量控制

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

优化点

  • 多线程分片上传提升速度。
  • 压缩分片减少传输量。
  • 客户端本地计算分片哈希,服务端校验完整性。

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

回答:
核心策略:

  1. 缓存更新策略
    写穿透(Write Through):先更新数据库,再更新缓存。
    延迟双删:更新数据库后,先删缓存,延迟再删一次(防旧数据回填)。

  2. 失效机制
    • 设置合理TTL,结合惰性删除(访问时检查过期)。
    • 主动刷新热点数据,避免批量失效(缓存雪崩)。

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

  4. 最终一致性
    • 通过消息队列异步更新缓存(如数据库变更后发送MQ)。

容错设计

  • 降级策略:缓存失效时直接返回默认值或限流。
  • 监控告警:缓存命中率、延迟异常时触发告警。

欢迎关注 ❤

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

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

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


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

115 次点击  ∙  1 赞  
加入收藏 微博
1 回复  |  直到 2025-03-26 10:14:38
wangzhongyang007
wangzhongyang007 · #1 · 大约22小时之前

你觉得这个难度怎么样?

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传