Q1:什么是互斥?什么是同步?两者有什么区别?
  • 核心要点

    • 互斥:多个线程/进程访问临界资源时,同一时刻只允许一个执行单元进入临界区。
    • 同步:多个线程/进程之间需要按照某种先后次序或条件协同执行。
    • 互斥解决的是 “不能同时做” 的问题。
    • 同步解决的是 “必须按顺序做” 的问题。
    • 典型例子:
      • 互斥:多个线程同时修改同一个计数器。
      • 同步:生产者先生产,消费者后消费。
  • 易错点

    • 把互斥和同步当成同义词。
    • 认为“加锁”就一定是同步,实际上很多锁主要是为了解决互斥。
    • 忽略“临界资源”和“临界区”的区别:
      • 临界资源:共享资源本身。
      • 临界区:访问共享资源的那段代码。
  • 深挖点

    • 为什么互斥也属于一种特殊的同步?
    • 在 Kubernetes 场景中,哪些问题更偏互斥,哪些更偏同步?
      • 例如共享状态写入偏互斥,控制器事件处理顺序偏同步。
    • 用户态并发问题和内核态并发问题有何不同?
Q2:实现互斥常见有哪些机制?各自适用什么场景?
  • 核心要点

    • 常见机制:
      • 锁:互斥锁(mutex)
      • 自旋锁:spinlock
      • 信号量:semaphore
      • 读写锁:rwlock
      • 原子操作:CAS、test-and-set
      • 关闭中断:仅适用于内核某些场景,不适用于普通用户态程序
    • 适用场景:
      • mutex:临界区较长,线程可能睡眠,适合大多数用户态场景。
      • spinlock:临界区很短,不希望线程睡眠,常用于内核、多核短等待场景。
      • rwlock:读多写少。
      • semaphore:既可用于互斥,也可用于资源计数与同步。
  • 易错点

    • 认为信号量只用于同步,不用于互斥。
    • 认为自旋锁一定比互斥锁快,实际上等待时间长时会浪费 CPU。
    • 混淆“二元信号量”和“互斥锁”。
  • 深挖点

    • 为什么自旋锁不适合单核长临界区?
    • 自旋锁和 mutex 在上下文切换开销上有什么差异?
    • Linux 内核为什么大量使用自旋锁,而用户态更常见 mutex?
Q3:什么是信号量?P/V 操作分别表示什么?
  • 核心要点

    • 信号量本质是一个整型计数器,用于控制并发访问和线程协作。
    • P 操作(wait/down):
      • 申请资源,信号量减 1。
      • 若结果小于 0 或资源不可用,则阻塞。
    • V 操作(signal/up):
      • 释放资源,信号量加 1。
      • 若有等待线程,则唤醒。
    • 类型:
      • 二元信号量:取值通常为 0/1,可实现互斥。
      • 计数信号量:可表示多个同类资源。
  • 易错点

    • 只记住加减 1,不理解其“资源计数”含义。
    • 不清楚 P/V 是原子操作。
    • 把信号量和条件变量混为一谈。
  • 深挖点

    • 为什么说信号量既能实现互斥也能实现同步?
    • 信号量初值如何设置?
      • 互斥:通常初始化为 1。
      • 资源池:初始化为资源数量。
      • 事件同步:可能初始化为 0。
    • 用信号量怎么解决生产者-消费者问题?
Q4:互斥锁、自旋锁、读写锁的区别是什么?
  • 核心要点

    • 互斥锁
      • 获取不到锁时,线程进入睡眠/阻塞。
      • 适合临界区较长的场景。
    • 自旋锁
      • 获取不到锁时,线程一直循环等待。
      • 适合临界区很短、锁持有时间很小的场景。
    • 读写锁
      • 允许多个读者并发进入。
      • 写者独占。
      • 适合读多写少场景。
  • 易错点

    • 认为读写锁一定优于互斥锁。
    • 忽略写饥饿、读饥饿问题。
    • 不考虑锁竞争激烈时的性能退化。
  • 深挖点

    • 什么情况下读写锁反而比 mutex 更慢?
    • 自旋锁为什么不能在持锁期间进行可能阻塞的操作?
    • 如果是 kube-apiserver 或 controller 这种高并发读场景,为什么会考虑读写锁或无锁结构?
Q5:什么是临界区?进入临界区需要满足哪些原则?
  • 核心要点

    • 临界区:访问共享资源的代码段。
    • 经典原则:
      • 空闲让进
      • 忙则等待
      • 有限等待
      • 让权等待(不能进入时应释放 CPU,而不是无休止占用)
    • 目标不仅是正确性,也包括公平性和效率。
  • 易错点

    • 只记“互斥”而忽略“有限等待”。
    • 把“忙则等待”和“自旋等待”简单等同。
    • 认为只要加锁就满足所有原则,实际上还可能饥饿。
  • 深挖点

    • 什么叫“有限等待”?为什么它对应公平性?
    • 自旋锁是否满足“让权等待”?
    • 如果某线程长期拿不到锁,这属于死锁还是饥饿?
Q6:什么是死锁?死锁产生的四个必要条件是什么?如何避免?
  • 核心要点

    • 死锁:多个进程/线程相互等待对方占有的资源,导致永久阻塞。
    • 四个必要条件:
      • 互斥
      • 请求并保持
      • 不可剥夺
      • 循环等待
    • 处理方法:
      • 预防:破坏四个条件之一
      • 避免:如银行家算法
      • 检测与解除
    • 常见避免方式:
      • 固定加锁顺序
      • 一次性申请全部资源
      • 设置超时与回退机制
  • 易错点

    • 把死锁和饥饿混淆。
    • 只会背四条件,不会结合代码举例。
    • 认为“线程卡住”就一定是死锁,也可能是活锁、长时间阻塞或 IO 等待。
  • 深挖点

    • 两把锁交叉加锁如何形成死锁?
    • 在 Linux 排查死锁时,你会看什么?
      • 线程栈
      • 锁依赖
      • 进程状态
    • 分布式系统里有没有“类似死锁”的问题?
Q7:什么是生产者-消费者问题?如何用信号量实现?
  • 核心要点

    • 这是同步与互斥结合的经典问题。
    • 需要三个信号量:
      • mutex:互斥访问缓冲区
      • empty:空缓冲区数量
      • full:已占用缓冲区数量
    • 逻辑:
      • 生产者:先申请 empty,再申请 mutex,放入数据后释放 mutex,再增加 full
      • 消费者:先申请 full,再申请 mutex,取出数据后释放 mutex,再增加 empty
  • 易错点

    • P/V 顺序写反,导致死锁。
    • 只加 mutex,不加 empty/full,导致同步关系缺失。
    • 把“资源个数控制”和“临界区保护”混为一类。
  • 深挖点

    • 如果缓冲区是无界的,还需要 empty 吗?
    • 多生产者多消费者下如何保证正确性?
    • 在实际 Linux 服务中,消息队列、ring buffer 与该模型有什么关系?
Q8:什么是读者-写者问题?重点考什么?
  • 核心要点

    • 目标:
      • 多个读者可以并发读
      • 写者必须独占
    • 考点通常有三类策略:
      • 读者优先
      • 写者优先
      • 公平策略
    • 本质是对并发访问的策略权衡:吞吐量、公平性、延迟。
  • 易错点

    • 只会说“读写锁适合读多写少”,不会说具体问题。
    • 不理解“写者饥饿”。
    • 忽略公平锁和非公平锁的差异。
  • 深挖点

    • 为什么读者优先容易导致写者饥饿?
    • 操作系统或数据库是如何处理读写竞争的?
    • etcd、缓存系统、配置中心这类组件为什么经常涉及读写一致性问题?
Q9:什么是条件变量?它与信号量有什么区别?
  • 核心要点

    • 条件变量用于线程等待某个条件成立,本身不保护共享资源,通常必须配合 mutex 使用。
    • 操作:
      • wait:释放锁并睡眠
      • signal / broadcast:通知等待线程
    • 与信号量区别:
      • 信号量是资源计数
      • 条件变量是条件等待/事件通知
    • 条件变量更强调“某个状态是否满足”。
  • 易错点

    • 认为条件变量可以单独实现互斥。
    • 不知道 wait 需要和 mutex 配合。
    • 不会解释为什么 wait 之后要用 while 再检查条件。
  • 深挖点

    • 为什么要防止“虚假唤醒”?
    • signalbroadcast 的区别是什么?
    • 在容器运行时、任务调度、线程池里,条件变量有哪些实际使用场景?
Q10:什么是原子操作?CAS 能解决什么问题?有什么局限?
  • 核心要点

    • 原子操作:执行过程中不会被中断到只完成一半的操作。
    • CAS(Compare-And-Swap):
      • 比较内存值是否等于预期值
      • 若相等则更新为新值
      • 否则失败重试
    • 可用于:
      • 无锁计数器
      • 自旋实现
      • 乐观并发控制
    • 优点:减少锁开销,提高并发性能。
  • 易错点

    • 认为原子操作能替代所有锁。
    • 不知道 ABA 问题。
    • 忽略高竞争下 CAS 重试导致 CPU 浪费。
  • 深挖点

    • 什么是 ABA 问题?怎么解决?
    • CAS 为什么常和自旋一起出现?
    • 无锁编程为什么难?难在内存可见性、顺序性还是正确性证明?
Q11:进程同步和线程同步有何区别?面试中怎么回答更稳?
  • 核心要点

    • 线程共享同一进程地址空间,同步主要围绕共享内存展开。
    • 进程地址空间独立,同步常依赖:
      • 共享内存 + 锁
      • 管道
      • 消息队列
      • socket
      • 信号量
    • 线程切换开销一般比进程小,但线程同步更容易出现共享数据竞争。
  • 易错点

    • 只说“线程轻量级,进程重量级”,没有落到同步机制。
    • 忽略进程间同步往往伴随 IPC。
    • 不清楚“共享内存是最快的 IPC,但需要额外同步机制”。
  • 深挖点

    • 为什么多线程程序更容易出现竞态条件?
    • 多进程和多线程在容器环境下分别有什么运维视角差异?
    • kubelet、containerd 相关组件更偏多线程还是多进程协作?
Q12:什么是竞态条件?如何分析和避免?
  • 核心要点

    • 竞态条件:程序结果依赖多个执行流的执行时序,且这种时序不可控。
    • 常见表现:
      • 计数错误
      • 状态错乱
      • 数据覆盖
      • 重复执行
    • 避免方式:
      • 加锁
      • 原子操作
      • 减少共享状态
      • 消息传递
      • 明确内存可见性模型
  • 易错点

    • 把所有并发 bug 都归结为死锁。
    • 只考虑“正确性”,不考虑“性能”和“锁粒度”。
    • 认为单核就没有竞态,实际上线程切换同样会导致竞态。
  • 深挖点

    • 锁粒度过大和过小分别有什么问题?
    • 怎样设计一个线程安全的计数器?
    • 在 K8s 运维中,日志采集、指标采集、控制循环中有哪些竞态风险?

速记笔记:互斥与同步

  • 互斥:同一时刻只能一个执行流进入临界区。
  • 同步:多个执行流按约定顺序协作执行。
  • 临界区:访问共享资源的代码段。
  • 常见机制:
    • mutex:阻塞等待
    • spinlock:忙等,自旋
    • semaphore:计数 + 同步/互斥
    • rwlock:读共享,写独占
    • CAS:原子更新,乐观并发
  • 经典问题:
    • 生产者-消费者
    • 读者-写者
    • 哲学家进餐
  • 死锁四条件:
    • 互斥
    • 请求并保持
    • 不可剥夺
    • 循环等待
  • 面试高频混淆点:
    • 互斥 vs 同步
    • mutex vs semaphore
    • mutex vs spinlock
    • 死锁 vs 饥饿 vs 活锁
    • 条件变量 vs 信号量
  • 答题口诀:
    • 先说定义
    • 再说机制
    • 再说场景
    • 再说风险:竞态、饥饿、死锁
    • 最后补一句 Linux / K8s 中的实际理解