面灵AI→

百度后端开发岗面经 10:内存池、智能指针与手撕汇总

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍
  2. 介绍一下内存池项目,参考 Tcmalloc 参考在哪里?
  3. 智能指针了解吗?实现一个链表用智能指针还是裸指针,用智能指针好处在哪里、代价在哪里?智能指针用独占还是共享的?双向链表用什么指针,节点之间用什么指针?
  4. 共享指针的内存管理是线程安全的吗?
  5. AI 应用服务平台用了哪些模型,会话管理是怎么做的,会话里面用了什么数据结构?
  6. 里面还做了多模态和语音生成,图像识别用的是什么模型,是深度学习模型还是大语言模型,做目标检测的嘛?
  7. 介绍一下实习的项目背景、痛点、需求和实现
  8. 实习的任务里面有 Agent 参与嘛?追问:为什么开发服务端诊断工具?
  9. 手撕:马走日(BFS)
  10. 为什么要设计核心线程池?核心线程数和最大线程数这两个区别是什么,能不能只有最大线程数?
  11. 怎么查看 CPU 和内存的利用率?
  12. 怎么查看 IO 使用情况?
  13. 自我介绍
  14. 你当时是基于一个怎么样的考虑去做的这个事情?
  15. 你说 malloc 有锁竞争过大、内存碎片过多、频繁申请 OS 的问题,你是怎么解决的?
  16. 如果一个软件在跑的时候被怀疑可能会有内存泄漏相关的问题,你是一个怎么样的思路去排查?
  17. 有哪些可以削峰填谷的方法?
  18. 你的服务器是怎么和客户端通信的?
  19. 你说通过 protobuf 序列化约定通信协议,再发出去,有没有考虑过其他的方法呢?
  20. 请做一下自我介绍
  21. 请介绍 Go 的逃逸分析
  22. RAG 与 Agent ReAct 分别适用于什么场景?
  23. 什么情况下使用 LangChain,什么情况下使用 LangGraph?
  24. 手撕:使用条件变量实现线程安全的生产者—消费者队列,功能类似 Go Channel
  25. 手撕:实现带 TTL 的 LRU 缓存
  26. 实习项目中的 AI 代码占比

《参考解析》

1. 共享指针的线程安全边界。shared_ptr 只保证引用计数本身是线程安全的:多个线程同时对同一个 shared_ptr 实例做拷贝、赋值、析构,计数不会错乱,底层靠原子操作实现。但它不保证指向的对象线程安全,也不保证「同一个 shared_ptr 变量被多线程同时读写」安全——两个线程同时改写同一个 shared_ptr 实例,读写指针与计数的组合不是原子的,需要额外加锁。这是这道题的得分点,只说「线程安全」或只说「不安全」都不完整。

2. 链表该用智能指针还是裸指针。单向链表通常用 unique_ptr 表示所有权,next 就是唯一的独占关系,析构自动递归释放。双向链表麻烦在 prev 与 next 互相指向,如果两边都用 shared_ptr 会形成循环引用、计数永远不归零,所以惯例是「next 用 unique_ptr 表现所有权,prev 用裸指针表现观察关系」,节点指针同理——谁拥有谁用智能指针,谁只是引用谁就用裸指针。用智能指针的好处是异常安全和自动释放,代价是每个节点多一个指针大小的开销、以及 shared_ptr 的原子计数在高频访问下有明显成本。

3. 为什么要有核心线程数。这是「按需扩容 + 控制常态成本」的设计:核心线程数代表常驻的基线并发,创建后默认不回收,避免流量抖动时反复创建销毁线程;最大线程数是突发上限,超出核心的那部分用完后会按 keepAliveTime 回收。如果只保留最大线程数,要么一上来就创建那么多线程(空闲时白占内存和调度开销),要么每次都临时创建(创建线程本身就有成本,突发时反而更慢)。追问「队列满了之后怎么走」时要能答出拒绝策略。

4. 查看 CPU、内存与 IO。CPU 和内存用 top(1 展开每核、M 按内存排序)、vmstat 1 看上下文切换与阻塞进程数、free -h 看内存与 swap。IO 用 iostat -x 1 看每块盘的 util 和 await,iotop 定位是哪个进程在写,df -h 看空间、lsof 看被占用的文件句柄。真正区分「CPU 密集还是 IO 密集」的关键指标是 vmstat 里的 wa(等待 IO 的 CPU 占比)和 iostat 的 await 是否升高。

5. 内存泄漏的排查思路。先确认是泄漏还是正常增长:观察 RSS 随时间是否单调上升、GC 后是否回落。C++ 里优先上工具——Valgrind memcheck 抓未释放,或者 ASan / LSan 在测试环境直接报堆栈;生产环境不敢挂工具就用 malloc 计数器、按模块统计分配数减去释放数,把差值定位到模块。Java 那边是导出堆 dump 用 MAT 看支配树。排查顺序永远是「先确认现象、再缩小范围、最后定位持有链」,而不是直接看代码猜。

6. 削峰填谷的常见手段。入口限流(令牌桶、漏桶)把超过处理能力的流量挡掉或排队;消息队列做异步缓冲,把瞬时高峰摊到更长的时间窗消费;数据库写入批量合并,把 N 次小事务攒成一次大批量;读路径加缓存和本地缓存,把热点请求从后端摘走;非核心功能降级,在高峰期只保留主链路。答题时点明削峰的前提是「业务能接受延迟」,如果用户必须拿到同步结果,队列就不是削峰而是增加延迟。

7. 三道手撕的要点。马走日用 BFS 求最短步数,八个方向的偏移数组 + 访问标记,注意棋盘边界;条件变量版生产者消费者要写对 while 循环而不是 if(防止虚假唤醒),队列空时消费者等、满时生产者等,退出时用 notify_all;带 TTL 的 LRU 用哈希表 + 双向链表,TTL 的实现方式有两种——每个节点存过期时间戳、读取时惰性判断,或者额外维护一个按过期时间排序的小顶堆做主动清理,后者内存更可控但要处理堆与链表的同步。