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再检查条件。
深挖点
- 为什么要防止“虚假唤醒”?
signal和broadcast的区别是什么?- 在容器运行时、任务调度、线程池里,条件变量有哪些实际使用场景?
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 中的实际理解