#二、操作系统高频面试题
#151. 线程越多越好吗?为什么高并发服务不等于开更多线程?
#知识点
- 线程数与 CPU 核数关系
- 上下文切换
- 锁竞争
- CPU 密集型 vs IO 密集型
#详细解答
线程不是越多越好。线程数增加到一定程度后,系统会开始把大量时间花在调度、切换和同步上,而不是实际工作上。
在不同 workload 下结论也不同:
- CPU 密集型:线程数通常接近核数更合适;
- IO 密集型:线程数可以更多,但也可能更适合事件驱动模型;
- 极高并发短任务:线程池常比无限开线程更稳。
所以高并发不等于高线程数,关键是并发模型和任务类型是否匹配。
#152. 为什么多线程程序容易出现 race condition(竞态条件)?
#知识点
- 共享状态
- 非原子操作
- 指令交错执行
- 结果依赖调度时序
#详细解答
竞态条件的本质是:多个线程同时读写共享状态,而操作不是原子的,最终结果依赖不可控的执行时序。
例如一个看似简单的 count++,底层可能包含:
- 读当前值;
- 加一;
- 写回。
两个线程交错执行时,就可能发生丢失更新。高分回答要强调:race condition 不是“线程多就必然有”,而是“共享状态 + 非原子操作 + 不可控调度”三者叠加的结果。
#153. 锁为什么会影响性能?什么时候不该盲目加大锁粒度?
#知识点
- 临界区串行化
- 锁竞争
- cache line 抖动
- 粗粒度锁简单但吞吐差
#详细解答
锁保证正确性,但代价是把部分并发执行变成串行执行。锁影响性能的原因主要有:
- 线程等待;
- 上下文切换;
- 热点锁导致吞吐下降;
- cache line 抖动和伪共享。
粗粒度锁实现简单、容易保证正确,但会严重压缩并发性;细粒度锁并发性更好,但复杂度和死锁风险更高。真正合理的策略不是“一味细”或“一味粗”,而是按共享状态热度和访问模式来设计。
#154. 什么是条件变量?它解决了什么问题?
#知识点
- 等待某个条件成立
- 与互斥锁配合使用
- 生产者-消费者模型
- 避免忙等
#详细解答
条件变量的作用是:让线程在某个条件不满足时阻塞等待,而不是在那里不断轮询。它通常和互斥锁一起使用,用来协调线程之间的状态变化。
它解决的关键问题不是“共享数据保护”,而是“状态变化等待”。保护共享数据靠锁,等待某个条件成立靠条件变量,这两个概念要分清。
#155. 什么是生产者-消费者问题?常见的解决方法有哪些?
#知识点
- 有界缓冲区
- 生产速度和消费速度不一致
- 同步 + 互斥
- 条件变量 / 信号量 / 阻塞队列
#详细解答
生产者-消费者问题说的是:生产者不断放数据,消费者不断取数据,但缓冲区容量有限,双方速度又可能不一致。
要解决两类问题:
- 互斥:多个线程不能把队列结构搞坏;
- 同步:队列空时不能消费,队列满时不能继续生产。
常见解法有:
- 互斥锁 + 条件变量;
- 信号量;
- 语言或框架内置的阻塞队列。
这道题的核心不是背模板,而是你能不能把“共享数据保护”和“等待条件变化”拆开讲清。
#156. 什么是零拷贝(zero-copy)?为什么高性能网络服务关心它?
#知识点
- 尽量减少用户态/内核态数据拷贝
- 减少 CPU 搬运成本
- 常见手段:
sendfile、mmap - 提高吞吐,降低 CPU 占用
#详细解答
零拷贝并不总是字面意义上的“零次拷贝”,更准确地说,是尽量减少不必要的数据搬运,尤其减少内核缓冲区和用户缓冲区之间的复制。
高性能网络服务关心它,是因为大流量下 CPU 很容易花大量时间在“搬数据”而不是“处理数据”。通过 sendfile、mmap 等机制,可以降低 CPU 开销、减少上下文切换和缓存污染,提高吞吐。
所以零拷贝的价值不是概念时髦,而是:把 CPU 从数据搬运里解放出来。
#157. 为什么会出现内存碎片?内部碎片和外部碎片有什么区别?
#知识点
- 动态分配导致空间利用不连续
- 固定粒度与实际需求不匹配
- 内部碎片 vs 外部碎片
#详细解答
内存碎片出现的根因是:内存分配和释放不是完美连续、完美匹配的,时间一长就会出现浪费空间。
- 内部碎片:分配给对象的块比实际所需大,多出来的部分没法用。
- 外部碎片:总空闲空间够,但被切成很多零散小块,无法满足较大的连续分配请求。
所以分配器设计一直在权衡:分配速度、碎片控制和空间利用率。
#158. 什么是 IO 多路复用?select、poll、epoll 的核心区别是什么?
#知识点
- 一个线程等待多个 IO 事件
select/poll需要遍历epoll有就绪队列,更适合高并发- 一连接一线程不是唯一并发模型
#详细解答
IO 多路复用的目标是:用较少线程管理大量连接,而不是每个连接都绑一个线程。
三者区别可以这样理解:
select:有 fd 上限,每次都要拷贝和遍历;poll:没有固定 fd 上限,但仍然要线性遍历;epoll:把“关注哪些事件”和“哪些事件已就绪”分离,通常更适合高并发。
所以 epoll 的核心优势不是“接口新”,而是事件通知模型更适合大量连接。
#159. 什么是僵尸进程和孤儿进程?
#知识点
- 子进程结束但父进程未回收
- 父进程退出后子进程被托管
wait/waitpid
#详细解答
僵尸进程是子进程已经结束,但父进程还没调用 wait/waitpid 回收其退出状态,此时子进程虽然不再运行,但内核还保留着其进程表项。
孤儿进程则是父进程先退出,子进程还在运行,这时子进程通常会被 init/systemd 接管。
这道题的关键在于理解:进程退出不等于内核立即彻底清理掉所有记录,退出状态仍然需要被回收。
#160. 如果让你排查一个服务 CPU 很高、内存也涨得快的问题,你的操作系统视角排查顺序是什么?
#知识点
- 先看 CPU 和内存谁是主因
- 线程数、上下文切换、锁竞争、忙等
- RSS、虚拟内存、页错误、OOM 风险
- 系统层和应用层结合分析
#详细解答
一个更稳的排查顺序通常是:
- 先看宏观指标:先判断 CPU 是单核热点还是多核普涨,内存是 RSS 涨、缓存涨还是虚拟内存涨。
- 再看线程和调度:线程数是否异常、上下文切换是否过多、是否存在锁竞争或忙等。
- 再看内存行为:是对象分配过快、泄漏、缓存失控,还是缺页过多。
- 最后结合应用逻辑:是不是某个请求模式、某个模块、某段循环造成。
高分回答的关键不是背一串命令,而是体现出:你知道操作系统层看到的现象,如何一步步映射回应用问题。