#二、操作系统高频面试题

#151. 线程越多越好吗?为什么高并发服务不等于开更多线程?

#知识点

  • 线程数与 CPU 核数关系
  • 上下文切换
  • 锁竞争
  • CPU 密集型 vs IO 密集型

#详细解答

线程不是越多越好。线程数增加到一定程度后,系统会开始把大量时间花在调度、切换和同步上,而不是实际工作上。

在不同 workload 下结论也不同:

  • CPU 密集型:线程数通常接近核数更合适;
  • IO 密集型:线程数可以更多,但也可能更适合事件驱动模型;
  • 极高并发短任务:线程池常比无限开线程更稳。

所以高并发不等于高线程数,关键是并发模型和任务类型是否匹配。

#152. 为什么多线程程序容易出现 race condition(竞态条件)?

#知识点

  • 共享状态
  • 非原子操作
  • 指令交错执行
  • 结果依赖调度时序

#详细解答

竞态条件的本质是:多个线程同时读写共享状态,而操作不是原子的,最终结果依赖不可控的执行时序。

例如一个看似简单的 count++,底层可能包含:

  1. 读当前值;
  2. 加一;
  3. 写回。

两个线程交错执行时,就可能发生丢失更新。高分回答要强调:race condition 不是“线程多就必然有”,而是“共享状态 + 非原子操作 + 不可控调度”三者叠加的结果。

#153. 锁为什么会影响性能?什么时候不该盲目加大锁粒度?

#知识点

  • 临界区串行化
  • 锁竞争
  • cache line 抖动
  • 粗粒度锁简单但吞吐差

#详细解答

锁保证正确性,但代价是把部分并发执行变成串行执行。锁影响性能的原因主要有:

  1. 线程等待;
  2. 上下文切换;
  3. 热点锁导致吞吐下降;
  4. cache line 抖动和伪共享。

粗粒度锁实现简单、容易保证正确,但会严重压缩并发性;细粒度锁并发性更好,但复杂度和死锁风险更高。真正合理的策略不是“一味细”或“一味粗”,而是按共享状态热度和访问模式来设计。

#154. 什么是条件变量?它解决了什么问题?

#知识点

  • 等待某个条件成立
  • 与互斥锁配合使用
  • 生产者-消费者模型
  • 避免忙等

#详细解答

条件变量的作用是:让线程在某个条件不满足时阻塞等待,而不是在那里不断轮询。它通常和互斥锁一起使用,用来协调线程之间的状态变化。

它解决的关键问题不是“共享数据保护”,而是“状态变化等待”。保护共享数据靠锁,等待某个条件成立靠条件变量,这两个概念要分清。

#155. 什么是生产者-消费者问题?常见的解决方法有哪些?

#知识点

  • 有界缓冲区
  • 生产速度和消费速度不一致
  • 同步 + 互斥
  • 条件变量 / 信号量 / 阻塞队列

#详细解答

生产者-消费者问题说的是:生产者不断放数据,消费者不断取数据,但缓冲区容量有限,双方速度又可能不一致。

要解决两类问题:

  1. 互斥:多个线程不能把队列结构搞坏;
  2. 同步:队列空时不能消费,队列满时不能继续生产。

常见解法有:

  • 互斥锁 + 条件变量;
  • 信号量;
  • 语言或框架内置的阻塞队列。

这道题的核心不是背模板,而是你能不能把“共享数据保护”和“等待条件变化”拆开讲清。

#156. 什么是零拷贝(zero-copy)?为什么高性能网络服务关心它?

#知识点

  • 尽量减少用户态/内核态数据拷贝
  • 减少 CPU 搬运成本
  • 常见手段:sendfilemmap
  • 提高吞吐,降低 CPU 占用

#详细解答

零拷贝并不总是字面意义上的“零次拷贝”,更准确地说,是尽量减少不必要的数据搬运,尤其减少内核缓冲区和用户缓冲区之间的复制。

高性能网络服务关心它,是因为大流量下 CPU 很容易花大量时间在“搬数据”而不是“处理数据”。通过 sendfilemmap 等机制,可以降低 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 风险
  • 系统层和应用层结合分析

#详细解答

一个更稳的排查顺序通常是:

  1. 先看宏观指标:先判断 CPU 是单核热点还是多核普涨,内存是 RSS 涨、缓存涨还是虚拟内存涨。
  2. 再看线程和调度:线程数是否异常、上下文切换是否过多、是否存在锁竞争或忙等。
  3. 再看内存行为:是对象分配过快、泄漏、缓存失控,还是缺页过多。
  4. 最后结合应用逻辑:是不是某个请求模式、某个模块、某段循环造成。

高分回答的关键不是背一串命令,而是体现出:你知道操作系统层看到的现象,如何一步步映射回应用问题。