百度后端开发岗面经 10:内存池、智能指针与手撕汇总
- 轮次
- 多轮面试合集
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 自我介绍
- 介绍一下内存池项目,参考 Tcmalloc 参考在哪里?
- 智能指针了解吗?实现一个链表用智能指针还是裸指针,用智能指针好处在哪里、代价在哪里?智能指针用独占还是共享的?双向链表用什么指针,节点之间用什么指针?
- 共享指针的内存管理是线程安全的吗?
- AI 应用服务平台用了哪些模型,会话管理是怎么做的,会话里面用了什么数据结构?
- 里面还做了多模态和语音生成,图像识别用的是什么模型,是深度学习模型还是大语言模型,做目标检测的嘛?
- 介绍一下实习的项目背景、痛点、需求和实现
- 实习的任务里面有 Agent 参与嘛?追问:为什么开发服务端诊断工具?
- 手撕:马走日(BFS)
- 为什么要设计核心线程池?核心线程数和最大线程数这两个区别是什么,能不能只有最大线程数?
- 怎么查看 CPU 和内存的利用率?
- 怎么查看 IO 使用情况?
- 自我介绍
- 你当时是基于一个怎么样的考虑去做的这个事情?
- 你说 malloc 有锁竞争过大、内存碎片过多、频繁申请 OS 的问题,你是怎么解决的?
- 如果一个软件在跑的时候被怀疑可能会有内存泄漏相关的问题,你是一个怎么样的思路去排查?
- 有哪些可以削峰填谷的方法?
- 你的服务器是怎么和客户端通信的?
- 你说通过 protobuf 序列化约定通信协议,再发出去,有没有考虑过其他的方法呢?
- 请做一下自我介绍
- 请介绍 Go 的逃逸分析
- RAG 与 Agent ReAct 分别适用于什么场景?
- 什么情况下使用 LangChain,什么情况下使用 LangGraph?
- 手撕:使用条件变量实现线程安全的生产者—消费者队列,功能类似 Go Channel
- 手撕:实现带 TTL 的 LRU 缓存
- 实习项目中的 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 的实现方式有两种——每个节点存过期时间戳、读取时惰性判断,或者额外维护一个按过期时间排序的小顶堆做主动清理,后者内存更可控但要处理堆与链表的同步。