信号量基础入门:并发控制的核心概念

问题的复杂性产生的根本原因在于,如 2.2 节所述,共享变量的访问始终是“单向信息流”。也就是说,一个进程可以分配新值或检查当前值,但这种检查不会为其他进程留下任何痕迹。结果是,当一个进程想要对共享变量的当前值作出反应时,在检查和随后执行反应之间,该值可能已被其他进程更改。换句话说,现有的通信机制对于手头的问题来说是不充分的,我们需要寻找更合适的替代方案。

这种替代方案通过以下方式引入:
a) 在共享变量中引入特殊用途的整数,我们称之为“信号量(semaphores)”。
b) 在构成各个进程的动作集合中添加两种新的基本操作,我们分别称之为“P 操作”和“V 操作”。这些操作始终作用于信号量,并代表并发进程访问信号量的唯一方式。

信号量本质上是非负整数。如果仅用于解决互斥(Mutual Exclusion)问题,则其值的范围甚至可以限制为“0”和“1”。荷兰物理学家兼计算机设计师 C.S. Scholten 博士证明了信号量在取更大值时具有广泛的应用价值。需要区分时,我们会将它们分别称为“二进制信号量(binary semaphores)”和“通用信号量(general semaphores)”。我接下来给出的 P 操作和 V 操作的定义不受这种区别的影响。

——Dijkstra 的笔记 EWD 123

引言

事实上,信号量是一个难以理解的概念。它是同步问题的核心,与互斥锁一起是最先学习的概念之一,但初次接触时往往难以理解。

通常,信号量可以用以下方式总结:

“信号量是一种通过 P 和 V 这两个特殊的原子操作来操作表示资源可用性的计数器,从而解决因共享资源并发访问而导致同步无法保证的问题的技术。”

但是,共享资源到底是什么?原子操作又是什么?资源的可用性、P 和 V 又是什么?
为了回答这些问题,我写下了这篇文章。

本文的目标是帮助理解并发编程的基础以及信号量的概念。

什么是共享资源?

当程序并行执行时,多个线程或进程同时访问的数据被称为“共享资源(shared resource)”。

  • 示例:内存中的变量、文件句柄、网络端口、队列等。

共享资源具有状态(state)。如果同时对这个状态进行访问和修改,可能会引发意外错误。

用打印机比喻来理解共享资源

假设有一台打印机。想象一下,两个人同时尝试使用这台打印机时会发生什么:

  • 如果 A 正在打印,而 B 在中途开始打印会怎样?
  • 可能会导致打印中断、输出重叠,或者打印出空白页。

这表明对共享资源的并发访问会引起冲突。

为什么需要原子操作?

那么,如何解决这个问题呢?
核心在于,即使多个线程同时访问,也要确保状态的一致性,即保证“原子性(Atomicity)”。

例如,将变量 x 加 1 的操作 x = x + 1 实际上可以分解为以下三个步骤:

  1. 读取 x 的当前值。
  2. 将其加 1。
  3. 将结果写回 x

即使是这种看似简单的操作,如果有其他进程在中间介入,结果可能会被破坏。例如,两个线程同时执行 x = x + 1 时,最终结果可能只增加 1 而不是预期的 2,甚至可能出现更少的增量。

这种互相竞争修改值的情况导致了意想不到的状态,这种情况被称为竞态条件(Race Condition)

即使是“看起来像一行代码的操作”,实际上也可能分为多个步骤执行,因此需要一种防止中间介入的手段。

这就是原子操作 的作用。原子操作是不可分割的单位操作,在执行过程中不允许任何外部干预。

再次用打印机比喻来理解

  • 当 A 向打印机发送打印请求时,只有在打印完全结束、失败或中止后,B 的请求才能开始处理。
  • 如果在 A 打印过程中 B 插入并开始打印,可能会导致输出混合或部分丢失的错误。
  • 因此,只有在“A 的打印状态”明确结束后,才能处理下一个请求(B),从而保证输出状态的一致性。

这就是原子性的要求条件。
在打印过程中,打印机的状态(State)应处于“使用中”的锁定(lock)状态,
只有在完全结束后才能进行下一个任务。

为了同时管理程序中的基于状态的资源 ,需要一个无法被中途打断的原子控制机制

好了,那么原子性是如何保证的呢?这就是我们今天的主题。

Dijkstra 的提议:什么是信号量?

特殊用途的整数(special-purpose integers)

正如之前打印机的例子所示,我们需要能够从外部明确控制和监控“正在使用中”的状态。然而,仅靠普通变量无法安全地管理这种状态。

原因如下:

  • 任何人都可以读取和写入变量。
  • 某个进程读取的值可能在下一刻被其他进程更改。
  • 换句话说,在读取值并对其做出反应的时间间隔内,状态可能会发生变化,而这种变化不会被反映出来。

因此,现有的方式是一种容易中断的“检查-决定-执行”流程。
为了克服这种结构性限制,Edsger W. Dijkstra 提出了一个新的解决方案。

引入一种特殊用途的整数作为共享变量,我们称之为“信号量(semaphores)”。
——Dijkstra, EWD 123

什么是信号量?

信号量(semaphore)不仅仅是一个简单的整数。
它是一个外部访问受到控制的、具有特殊用途的状态值。

其核心在于:“禁止直接访问,只能通过两种操作(P/V)间接控制。”

在构成各个进程的动作集合中,添加两种新的基本操作,我们分别称之为“P 操作”和“V 操作”。这些操作始终作用于信号量,并代表并发进程访问信号量的唯一方式。
——Dijkstra, EWD 123

这个特殊的整数只能通过以下两个操作进行操作:

  • P 操作 (Proberen,意为“测试”)
  • V 操作 (Verhogen,意为“增加”)

这两个操作遵循以下规则:

操作

含义

效果

P(s)

wait / acquire

如果信号量值为正,则减 1 并通过;如果为 0,则等待。

V(s)

signal / release

将信号量值增加 1。

这些操作始终以原子性 方式执行,即它们不会被任何中断或干扰打断。

这正是我们一直在寻找的“防止中途介入的机制”。
这就是信号量。

P/V 操作的定义

一个简单的实现如下:

// 信号量用整数值 s 表示
P(s): // waitwhile (s <= 0) wait;s = s - 1;V(s): // signals = s + 1; // 在此之后,如果有等待中的进程,需要唤醒它们

现代操作系统中,这一过程通过 futex、spinlock、sleep queue 等方式实现。

需要注意的是,在 P/V 操作伪代码中,s = s - 1s = s + 1while (s <= 0) wait; 条件检查部分必须作为不可分割的原子操作 执行。

如果 P(s)while (s <= 0) wait; 部分是通过持续占用 CPU 并反复检查条件是否满足的方式(即忙等待(busy-waiting 或 spin-waiting) ),那么这将非常低效地使用 CPU 资源。这是在浪费本可以用于其他有用任务的 CPU 时间。

因此,现代操作系统采用以下机制来更高效地处理这种“等待”过程,这些方法可以看作是现代异步处理的核心技术:
睡眠队列(Sleep Queue / Wait Queue)、上下文切换(Context Switching)、自旋锁(Spinlock)、以及 Futex(快速用户空间互斥锁)
这些方法的整体理论基础正是上述简单的操作。

总结一下,在现代操作系统中,信号量操作可以描述如下:

  • 1. 基本的等待/唤醒: 使用睡眠队列 高效地挂起和唤醒进程(避免忙等待)。
  • 2. 内部原子性保障: 在 P/V 操作短暂的执行期间,为了安全地修改信号量变量(如 s)或睡眠队列,内核内部可能会使用自旋锁 等机制。
  • 3. 性能优化(尤其是 Linux): 利用Futex ,当没有资源竞争时在用户空间快速处理,只有在发生竞争时才使用内核的睡眠队列功能,从而减少系统调用开销。

二进制信号量 vs 通用信号量

区分

含义

使用场景

二进制信号量

值:0 或 1

等同于互斥锁(Mutex)

通用信号量

值:0 或更大

有限资源(例如:数据库连接池)

  • C.S. Scholten 的通用信号量概念扩展。

  • 与互斥锁的区别: 拥有权限的概念 vs 状态驱动模型

让我们回到打印机的例子,使用信号量控制打印机。

在打印机示例中应用二进制信号量的过程如下:

  1. 用一个初始值为 s = 1 的信号量表示打印机的状态。
  2. 用户 A 调用 P(s),将 s 减为 0 并开始打印。
  3. 在用户 A 打印过程中,用户 B 调用 P(s),但由于 s <= 0,用户 B 进入等待状态。
  4. 用户 A 完成打印后调用 V(s),使 s = 1,用户 B 随即可以开始打印。

通过这种方式,信号量将资源的“状态”抽象为数字,并通过原子操作改变该数值,从而实现对并发的控制。

前面提到的二进制信号量 适用于办公室只有一台打印机的情况(互斥访问)。
s=1 表示“打印机可用”,s=0 表示“打印机正在使用”。

但是,如果办公室有多个相同性能的打印机(例如:3 台)该怎么办呢?
在这种情况下,虽然允许多个人同时使用打印机,但必须确保使用的打印机数量不会超过可用的数量。
这正是**通用信号量(General Semaphore)计数信号量(Counting Semaphore)**发挥作用的时候。

通用信号量 s 是一个非负整数值,用于表示可用资源的数量。

使用通用信号量控制 3 台打印机的示例
  1. 初始状态:

    • 办公室有 3 台打印机,因此信号量 s 的初始值设置为 3s = 3)。
    • 这表示“当前有 3 台可用的打印机”。
  2. 用户 A 请求使用打印机(执行 P(s) 操作):

    • 调用 P(s)
    • 当前 s 的值(3)大于 0,因此将 s 减 1(s = 2)。
    • 用户 A 分配到一台打印机并开始打印。(现在可用的打印机数量为 2)
  3. 用户 B 请求使用打印机(执行 P(s) 操作):

    • 调用 P(s)
    • 当前 s 的值(2)大于 0,因此将 s 减 1(s = 1)。
    • 用户 B 也分配到一台打印机并开始打印。(现在可用的打印机数量为 1)
  4. 用户 C 请求使用打印机(执行 P(s) 操作):

    • 调用 P(s)
    • 当前 s 的值(1)大于 0,因此将 s 减 1(s = 0)。
    • 用户 C 分配到一台打印机并开始打印。(现在可用的打印机数量为 0)
  5. 用户 D 请求使用打印机(执行 P(s) 操作):

    • 调用 P(s)
    • 当前 s 的值(0)小于或等于 0,因此用户 D 会在 P(s) 操作中的 while (s <= 0) wait; 条件下进入等待状态 ,直到有打印机可用。
  6. 用户 A 打印完成并归还打印机(执行 V(s) 操作):

    • 用户 A 完成打印后调用 V(s)
    • s 增加 1(s = 1)。(现在有 1 台打印机可用)
    • V(s) 操作完成后,之前在 P(s) 操作中等待的用户 D 被唤醒,并重新检查 s 的值。由于 s 现在为 1,用户 D 将 s 设置为 0 并开始使用打印机。

通用信号量的核心作用:

如上所述,通用信号量不仅仅是一个简单的二进制“锁定/解锁”机制,它还可以精确管理有限资源池的并发访问 ,并准确跟踪可用资源的数量

结语

信号量仍然是内核级同步的核心工具,同时也是基于状态的访问模型的典型代表。

信号量看似只是一个简单的整数,
但它却是系统中用于准确表示资源状态
安全控制资源 、以及以可预测的方式共享资源的唯一抽象手段

我们必须牢记,在这个小小的整数背后,
承载着无数进程的秩序、冲突避免和系统稳定性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80579.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(十九)Java集合框架深度解析:从基础到高级应用

一、集合框架概述 1.1 什么是集合框架 Java集合框架(Java Collections Framework, JCF)是Java语言中用于表示和操作集合的一套标准化体系结构。它提供了一组接口、实现类和算法&#xff0c;用于存储和操作对象组&#xff0c;解决了数组在存储对象时的诸多限制。 集合框架的主…

Blender cycles烘焙贴图笔记

下载了一些枪模型&#xff0c;一个模型有七八个材质&#xff0c;一个扳机、准星还有单独的材质&#xff0c;用的贴图只有一小部分有内容&#xff0c;对Draw Call非常不友好。不得不学一下怎么用Blender减材质。 找到了这个视频如何在Blender中将多种材料多张贴图烘焙成一张贴图…

mysql的高可用

1. 环境准备 2台MySQL服务器&#xff08;node1: 192.168.1.101&#xff0c;node2: 192.168.1.102&#xff09;2台HAProxy Keepalived服务器&#xff08;haproxy1: 192.168.1.103&#xff0c;haproxy2: 192.168.1.104&#xff09;虚拟IP&#xff08;VIP: 192.168.1.100&#x…

鸿蒙 系统-安全-程序访问控制-应用权限管控

Ability Kit 提供了一种允许应用访问系统资源&#xff08;如&#xff1a;通讯录等&#xff09;和系统能力&#xff08;如&#xff1a;访问摄像头、麦克风等&#xff09;的通用权限访问方式&#xff0c;来保护系统数据&#xff08;包括用户个人数据&#xff09;或功能&#xff0…

算法-数对的使用

1、数对可用于数组排序中&#xff0c;并且可记忆化排序前的元素下标 #include<iostream> #include<string> #include<bits/stdc.h> using namespace std; typedef long long ll; const int N 2e5 10; pair<int, int> a[N]; void solve() {ll n;cin …

Linux基础第四天

系统之间文件共享 想要实现两个不同的系统之间实现文件共享&#xff0c;最简单的一种方案就是设置VMware软件的共享文件夹&#xff0c;利用共享文件夹可以实现linux系统和windows系统之间的文件共享&#xff0c;这样就可以实现在windows系统上编辑程序&#xff0c;然后在linux系…

Docker 核心原理详解:Namespaces 与 Cgroups 如何实现资源隔离与限制

#Docker疑难杂症解决指南# Docker 作为容器化技术的代名词,彻底改变了软件的开发、部署和管理方式。它凭借其轻量、快速、一致性强的特性,成为了现代云原生架构的基石。然而,Docker 容器的神奇之处并非“无中生有”,其背后是 Linux 内核的两大核心技术——Namespaces(命名…

GitHub 趋势日报 (2025年05月14日)

本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1xming521/WeClone&#x1f680;从聊天记录创造数字分身的一站式解决方案&…

【Go】从0开始学习Go

文章目录 从0开始学习Go0 与C对比1 代码框架1.1 helloworld式代码示例1.2 主体代码元素&#xff08;核心三部分&#xff09;1.3 其他 2 与C/C区别3 有用的小工具4 注意事项 从0开始学习Go 0 与C对比 特性CGo编译型语言需要编译为机器码直接编译为二进制可执行文件静态类型类型…

简单说一下 Webpack分包

最近在看有关webpack分包的知识&#xff0c;搜索了很多资料&#xff0c;感觉这一块很是迷惑&#xff0c;网上的资料讲的也迷迷糊糊&#xff0c;这里简单总结分享一下&#xff0c;也当个笔记。 如有错误请指出。 为什么需要分包 我们知道&#xff0c;webpack的作用&#xff0c…

使用Python和FastAPI构建网站爬虫:Oncolo医疗文章抓取实战

使用Python和FastAPI构建网站爬虫&#xff1a;Oncolo医疗文章抓取实战 前言项目概述技术栈代码分析1. 导入必要的库2. 初始化FastAPI应用3. 定义请求模型4. 核心爬虫功能4.1 URL验证和准备4.2 设置HTTP请求4.3 发送请求和解析HTML4.4 提取文章内容4.5 保存结果和返回数据 5. AP…

YoloV8改进策略:卷积篇|风车卷积|即插即用

文章目录 论文信息论文翻译摘要引言相关研究红外搜索与跟踪检测和分割网络红外搜索与跟踪数据集的损失函数红外搜索与跟踪数据集方法风车形卷积(PConv)基于尺度的动态损失SIRST - UAVB数据集实验实验设置与其他方法的比较多模型上的消融实验结论致谢代码改进方法测试结果总结…

【NLP】36. 从指令微调到人类偏好:构建更有用的大语言模型

从指令微调到人类偏好&#xff1a;构建更有用的大语言模型 大语言模型&#xff08;LLMs&#xff09;已经成为现代自然语言处理系统的核心&#xff0c;但单纯依赖传统语言建模目标&#xff0c;往往难以满足实际应用的“人类意图”。从 Instruction Tuning&#xff08;指令微调&…

基于Transformers与深度学习的微博评论情感分析及AI自动回复系统

前言 这个项目存在cookie没有自动更新问题&#xff0c;后续可能会发出来解决教程&#xff0c;还有微博网页版的话最多看到300条评论&#xff0c;而且回复别人信息的话最多回复15条就要休息5分钟左右才能评论 1. 项目概述 本项目实现了一个微博评论自动化处理系统&#xff0c…

详解 Zephyr RTOS:架构、功能与开发指南

目录 Zephyr RTOS 的核心特性 1. 轻量级和可扩展性 2. 实时性能 3. 多平台支持 4. 安全性 5. 社区和生态系统 Zephyr 的架构 1. 内核 2. 驱动模型 3. 网络栈 4. 文件系统 开发环境和工具链 安装和配置 开发流程 1. 应用程序开发 2. 调试和测试 3. 部署 实际应…

人工智能重塑医疗健康:从辅助诊断到个性化治疗的全方位变革

人工智能正在以前所未有的速度改变着医疗健康领域&#xff0c;从影像诊断到药物研发&#xff0c;从医院管理到远程医疗&#xff0c;AI 技术已渗透到医疗服务的各个环节。本文将深入探讨人工智能如何赋能医疗健康产业&#xff0c;分析其在医学影像、临床决策、药物研发、个性化医…

Linux笔记---内核态与用户态

用户态&#xff08;User Mode&#xff09; 权限级别&#xff1a;较低&#xff0c;限制应用程序直接访问硬件或关键系统资源。 适用场景&#xff1a;普通应用程序的运行环境。 限制&#xff1a;无法执行特权指令&#xff08;如操作I/O端口、修改内存管理单元配置等&#xff09…

Spring 代理与 Redis 分布式锁冲突:一次锁释放异常的分析与解决

Spring 代理与 Redis 分布式锁冲突&#xff1a;一次锁释放异常的分析与解决 Spring 代理与 Redis 分布式锁冲突&#xff1a;一次锁释放异常的分析与解决1. 问题现象与初步分析2 . 原因探究&#xff1a;代理机制对分布式锁生命周期的干扰3. 问题复现伪代码4. 解决方案&#xff1…

SQL:多列匹配(Multiple-column Matching)

目录 基础概念 应用场景详解 1. 多列等值匹配 2. 多列 IN 匹配&#xff08;集合匹配&#xff09; 3. 多列 JOIN 匹配&#xff08;复合键连接&#xff09; 4. 多列匹配 子查询 5. 多列匹配 EXISTS 6. 多列匹配 UNION&#xff08;组合数据源&#xff09; 7. 多列匹配…

基于DeepSeek的智能客服系统实践与创新

引言:AI大模型重塑客户服务新范式 近年来,AI大模型技术的突破性进展正在深刻改变传统客户服务模式。作为国内领先的AI企业,DeepSeek凭借其创新的算法架构(如MoE混合专家模型、动态学习率调度器)和极致的成本效益(仅为同类模型成本的1/20),在自然语言理解、情感分析、多…