当前位置: 首页 > backend >正文

互斥锁详解(操作系统os)

1. 互斥锁 (Mutex) - 档案室的“智能锁”

首先,我们给之前讨论的那些“锁”一个正式的名字:互斥锁 (Mutex)

  • 概念:你可以把它简单理解成档案室门上的一把“智能锁”。它只有两种状态:locked (已上锁) 或 unlocked (未上锁)。
  • 操作:它提供两个标准操作:
    • acquire() (或 lock()):尝试获取锁。如果锁是开着的,你就把它锁上并进去。如果锁已经是锁着的状态,你就得等待。
    • release() (或 unlock()):你从档案室出来后,把锁打开。

这是一个非常通用的概念。之前我们学的所有方法,无论是软件的 Peterson 算法,还是硬件的 TSL/Swap 指令,它们本质上都是在实现一个“互斥锁”。

2. 互斥锁的“坏脾气”:忙等待与自旋锁 (Spinlock)

现在,关键问题来了:当 acquire() 失败时,进程该如何“等待”?

之前我们学到的所有软件和硬件实现方法,都有一个共同的特点:它们采用的是一种非常“执着”的等待方式。

  • 忙等待 (Busy-Waiting):当进程发现门是锁着的,它不会走开,而是在门口不停地、反复地检查:“门开了吗?开了吗?现在呢?”。这个过程,进程的CPU并没有闲着,而是在一个死循环里空转。
  • 自旋锁 (Spinlock):因为这种等待方式就像一个陀螺在原地不停地“旋转”一样,所以,我们把采用这种“忙等待”策略来实现的互斥锁,特别称为“自旋锁”。

所以,视频里提到的TSL指令、Swap指令,甚至Peterson算法,它们实现的都是自旋锁。它们都存在忙等待问题,违反了“让权等待”原则。


3. 自旋锁的成本与收益:一场“空转”与“切换”的赛跑

既然自旋锁会导致CPU空转,浪费资源,为什么我们还要用它呢?难道就没有更好的办法吗?

有!更好的办法就是我们之前提到的“让权等待”:进程发现门锁着,就去旁边的休息室睡觉(进入阻塞态),把CPU让给别人。等门开了,再由别人唤醒。

但是,“去睡觉再被叫醒”这个过程是有成本的,这个成本叫做“进程上下文切换”。它非常昂贵,好比:

  1. 你把办公桌上所有文件、电脑状态全部打包收好(保存现场)。
  2. 走到休息室(切换到内核态)。
  3. 找到一个空沙发躺下(进入阻塞队列)。
  4. 等别人叫醒你后,你再走回办公室(切换回用户态)。
  5. 再把所有文件和电脑状态全部恢复原样(恢复现场)。

这个过程比你单纯在门口站着“空转”几圈要复杂得多!

于是,我们就面临一个选择:

  • 选择自旋:付出“CPU空转”的代价。
  • 选择睡眠:付出“两次上下文切换”的代价。

到底哪个更划算?这取决于门要锁多久


4. 场景决定策略:单核 vs. 多核

这个选择在单核与多核系统上,答案是截然不同的。

在单处理机系统(一个打工人)
  • 场景:办公室只有一个打工人。他想进档案室,发现门被另一个任务锁着了。如果他选择“自旋”,会发生什么?
  • 灾难性后果:他会一直占用着办公室里唯一的CPU资源,在门口空转。而那个锁着门的任务,因为得不到CPU,根本无法运行,也就永远无法出来开门!只有等这个自旋的进程时间片用完,被强制换下,那个锁门进程才有机会上CPU去开锁。
  • 结论:在单核系统里,自旋等待毫无意义,纯属浪费。因为你等的那个锁,绝对不可能在你自旋的时候被解开。所以,在单核系统里,等待时必须“让权等待”(去睡觉)。
在多处理机系统(多个打工人)
  • 场景:办公室里有两个打工人(CPU 0 和 CPU 1)。进程A在CPU 0上运行,它想进档案室,发现门被正在CPU 1上运行的进程B锁着了。如果进程A选择“自旋”,会发生什么?
  • 可能的高效结果
    1. 进程A在CPU 0上开始自旋,占用了CPU 0。
    2. 与此同时,进程B正在CPU 1上继续运行!
    3. 如果进程B在档案室里的工作很简单,可能只需要几微秒就完成了。它在CPU 1上运行完,把门打开。
    4. CPU 0上的进程A在下一圈检查时,立刻就发现门开了,马上就能进去。
  • 结论:在这种情况下,进程A只“空转”了非常短的时间,这个代价远比进行一次昂贵的“上下文切换”要小得多。
  • 适用性:因此,自旋锁非常适合多处理器系统,但有一个重要前提:我们能预测锁被占用的时间非常短。比如,内核里修改一个指针,可能就几条指令的时间,用自旋锁就非常划算。

必会题与详解

题目一:什么是自旋锁?它与我们常说的“睡眠锁”(采用让权等待的锁)最核心的区别是什么?

答案详解

  1. 自旋锁 (Spinlock):是一种互斥锁的实现方式。当一个进程尝试获取锁失败时,它不会放弃CPU,而是进入一个“忙等待”循环,反复检查锁的状态,直到获取成功。
  2. 核心区别:它们在获取锁失败后的等待策略不同。
    • 自旋锁采用忙等待。进程保持在运行态,持续占用CPU进行空转。
    • 睡眠锁(如Semaphore、Mutex的非自旋实现)采用让权等待。进程会放弃CPU,从运行态转为阻塞态,进入等待队列,直到被其他进程唤醒。
    • 这个区别导致了它们的性能代价不同:自旋锁的代价是CPU空转时间,睡眠锁的代价是进程上下文切换的开销。

题目二:为什么说“自旋锁是为多处理器系统量身定做的”?在什么情况下,在多处理器系统中使用自旋锁是高效的?

答案详解

  1. 原-因:自旋锁的有效性依赖于一个核心前提:一个进程在等待锁的时候,另一个持有锁的进程能够同时在运行,以便尽快释放锁。这个“同时运行”的条件只有在多处理器系统中才能满足。在单处理器系统中,持有锁的进程无法与等待锁的进程同时运行,导致自旋等待变得毫无意义。

  2. 高效的情况:在多处理器系统中,当能够合理预期锁被占用的时间非常短时,使用自旋锁是高效的。因为如果锁很快被释放,那么等待进程自旋所消耗的CPU时间成本,将远小于进行两次昂贵的进程上下文切换(一次睡眠,一次唤醒)的成本。反之,如果锁被占用的时间很长,那么长时间的CPU空转会造成巨大浪费,此时采用睡眠锁让出CPU会更划算。

题目三:一个进程在使用自旋锁进行忙等待时,是否会一直霸占CPU直到它获得锁为止?请解释原因。

答案详解

不会。 一个用户态进程(或即使是内核态任务,在可抢占内核中)在使用自旋锁忙等待时,并不会无限期地霸占CPU。

原因是操作系统基于时间片轮转的抢占式调度机制依然在起作用。当该进程的时间片用完后,无论它是否在忙等待,时钟中断都会发生,调度程序会被触发,并强制将该进程从运行态切换下来,让它回到就绪队列。然后调度另一个进程上CPU运行。

所以,进程的忙等待只是在其被分配到的CPU时间片内进行空转。它并不能破坏操作系统的调度公平性,但它确实浪费了它“本应”用来做有意义计算的CPU时间。

http://www.xdnf.cn/news/15383.html

相关文章:

  • BERT系列模型
  • 前端工程化-构建打包
  • Flink数据流高效写入MySQL实战
  • Actor-Critic重要性采样原理
  • 九、官方人格提示词汇总(上)
  • 构造函数延伸应用
  • 数据结构 Map和Set
  • 一些git命令
  • SQL预编译:安全高效数据库操作的关键
  • Linux操作系统之信号概念启程
  • 【读书笔记】《C++ Software Design》第七章:Bridge、Prototype 与 External Polymorphism
  • IPC框架
  • [2025CVPR]GNN-ViTCap:用于病理图像分类与描述模型
  • 晋升指南-笔记
  • 【Docker基础】Dockerfile指令速览:环境与元数据指令详解
  • React强大且灵活hooks库——ahooks入门实践之状态管理类hook(state)详解
  • 【C++】多线程同步三剑客介绍
  • AutoLabor-ROS-Python 学习记录——第一章 ROS概述与环境搭建
  • leetGPU解题笔记(1)
  • STM32-第六节-TIM定时器-2(输出比较)
  • 【芯片笔记】ADF4159
  • 【论文阅读】AdaptThink: Reasoning Models Can Learn When to Think
  • 【Java Stream】基本用法学习
  • sql初学见解
  • 2025上海市“星光计划“信息安全管理与评估赛项二三阶段任务书
  • Spring高级特性——反射和动态代理的性能优化
  • Python---上下文管理器
  • 移动端设备本地部署大语言模型(LLM)
  • 无需付费即可利用AI消除音频噪声和生成字幕
  • 浏览器渲染原理与性能优化全解析