面试题答案
一键面试底层数据结构
- 基本标志位:最简单的形式是使用一个布尔值作为标志位。例如,0 表示锁未被持有,1 表示锁已被持有。但这种简单结构在多处理器环境下存在竞争问题,所以实际中会结合其他机制。
- 等待队列:当一个线程尝试获取已被持有的互斥锁时,它需要进入等待状态。操作系统通常会维护一个等待队列,将这些等待的线程节点加入队列中。例如,Linux 内核使用双向链表来实现等待队列,每个等待线程对应一个队列节点,节点中包含线程的相关信息。
硬件指令运用 - 原子操作指令
- 测试并设置(Test-and-Set):该指令会原子性地读取一个内存位置的值,然后将其设置为 1。例如,在 x86 架构中,有
xchg
指令可以实现类似功能。假设互斥锁用一个内存地址表示,初始值为 0。当一个线程执行xchg
指令时,它会原子性地将互斥锁地址的值与 1 交换,并返回旧值。如果返回旧值为 0,说明成功获取锁;如果返回旧值为 1,说明锁已被持有。 - 比较并交换(Compare-and-Swap,CAS):CAS 指令需要三个操作数,内存位置(目标地址)、预期值和新值。它会原子性地比较目标地址中的值与预期值,如果相等则将目标地址的值更新为新值,并返回成功;否则不更新并返回失败。在互斥锁场景下,预期值为 0(未持有锁),新值为 1(持有锁)。如果比较并交换成功,说明获取锁成功。
处理中断和上下文切换对互斥锁机制的影响
- 中断处理
- 中断屏蔽:在获取和释放互斥锁的关键代码段,可以屏蔽中断。例如,在 x86 架构中,可以通过
cli
指令关闭中断,在操作完成后使用sti
指令重新开启中断。这样做可以防止中断处理程序在关键代码段执行过程中抢占 CPU,避免破坏互斥锁的状态。 - 中断安全设计:即使在中断处理程序中,也需要遵循互斥锁的规则。如果中断处理程序可能访问共享资源,它也需要获取相应的互斥锁。但是要注意,中断处理程序不能长时间持有锁,以免影响系统响应性。
- 中断屏蔽:在获取和释放互斥锁的关键代码段,可以屏蔽中断。例如,在 x86 架构中,可以通过
- 上下文切换
- 内核调度机制:当一个线程持有互斥锁时,可能会因为时间片耗尽、更高优先级线程到来等原因发生上下文切换。操作系统内核调度器需要确保在上下文切换时,互斥锁的状态能被正确保存和恢复。
- 自旋锁与睡眠锁:自旋锁适用于锁持有时间较短的场景,持有自旋锁的线程在上下文切换时,其他线程会在原地自旋等待锁释放。而睡眠锁适用于锁持有时间较长的场景,当线程获取不到睡眠锁时会进入睡眠状态,让出 CPU,调度器会调度其他线程执行。这样可以避免线程在长时间等待锁时浪费 CPU 资源。