1. 为什么需要管程?—— 信号量 (Semaphore) 的困境

在理解管程之前,你必须先知道它要解决什么问题。之前,我们使用信号量 (Semaphore) 来实现进程/线程间的同步与互斥。

虽然信号量功能强大,但它存在两个主要问题:

  1. 编程复杂度高,容易出错:对信号量(P/Vwait/signal)操作的顺序至关重要。一旦顺序错了,就可能导致死锁或逻辑错误。编写正确的并发程序需要程序员有极高的技巧。

  2. 分散化管理:同步操作(PV)分散在程序的各个角落,而不是集中在一个模块中。这使得代码难以维护和理解。

管程就是为了解决这些问题而提出的一个更高级的同步机制。

2. 管程是什么?—— 一个形象的比喻

想象一个对象管理着一个共享资源(比如一个打印机)。这个对象有一个“接待室”(入口等待队列)。每次只允许一个“客户”(线程)进入“办公室”(管程内部)使用资源。

如果客户A正在办公室里使用打印机,客户B来了,他不能直接闯进去,必须在接待室排队。

如果客户A在使用过程中发现没纸了(条件不满足),他可以去“条件变量”这个专门的休息室里等待,并让出办公室。这时在接待室排队的客户B就可以进入办公室了。

客户B用完打印机后,可以帮客户A把纸装上,然后去休息室通知客户A:“纸有了!”。客户A从休息室出来,但此时它不能直接进办公室,因为客户B还在里面。它需要回到接待室重新排队,等客户B出来后,它才能再次进入办公室继续工作。

这个“办公室”及其管理规则,就是管程

3. 管程的正式定义与核心组件

管程是一种编程语言构件,它封装了:

  • 共享资源的数据结构(状态)。

  • 能操作共享资源的所有过程(函数/方法)

  • 在管程初始化时执行的代码

管程的核心特性是:互斥性。在任何时刻,最多只有一个线程能活跃在管程内(即正在执行管程的某个方法)。这是由编译器在编译管程时自动添加锁来实现的,无需程序员关心。

管程的几个关键组成部分:

  1. 互斥锁 (Lock):用于保证互斥访问。通常对程序员是透明的。

  2. 条件变量 (Condition Variables):这是管程实现同步的核心。因为互斥特性,当一个线程进入管程后,如果它发现某个条件不满足(如缓冲区空/满),它需要等待并让出管程的进入权。条件变量就是用于这种“等待”和“通知”的机制。

    • wait(cv, ...): 在一个条件变量 cv 上等待。调用该操作的线程会释放管程的互斥锁,并把自己挂到条件变量 cv 的等待队列上,进入睡眠状态。

    • signal(cv, ...) 或 notify(cv, ...): 唤醒一个在条件变量 cv 上等待的线程。如果有多个,则唤醒其中一个(具体唤醒哪个取决于策略)。

    • broadcast(cv, ...) 或 notifyAll(cv, ...): 唤醒所有在条件变量 cv 上等待的线程。

注意: 关于 signal 之后如何处理,有两种主流的管程风格:

  • Hoare 风格: 唤醒者立即让出管程,被唤醒的线程马上执行。逻辑清晰,但实现效率低。

  • Mesa 风格: 唤醒者继续执行,直到退出管程或再次等待。被唤醒的线程需要重新竞争锁,拿到锁之后才能继续执行。这意味着从被唤醒到再次运行,条件可能又被其他线程改变。因此,等待的条件检查必须使用 while 循环而不是 if 语句。Java 中的 synchronized 和 wait()/notify() 采用的是 MESA 风格

4. 代码讲解:生产者-消费者问题

我们用经典的生产者-消费者问题来展示管程的用法。这里我们用 Java 语言来示例,因为 Java 内建了类似于管程的同步机制 (synchronizedwaitnotifyAll)。

问题描述: 一个大小有限的缓冲区。生产者向缓冲区放入数据,消费者从缓冲区取出数据。

  • 同步条件1: 缓冲区满时,生产者必须等待 (buffer_full)。

  • 同步条件2: 缓冲区空时,消费者必须等待 (buffer_empty)。

首先,我们定义一个管程类 BoundedBuffer

public class BoundedBuffer {// 1. 共享资源的数据结构 (状态)private final int[] buffer;private int count; // 当前缓冲区中的数据量private int in;    // 生产者放入数据的位置private int out;   // 消费者取出数据的位置// 2. 管程的构造函数 (初始化代码)public BoundedBuffer(int size) {buffer = new int[size];count = 0;in = 0;out = 0;}// 3. 操作共享资源的过程/方法// 注意:这些方法都由 ‘synchronized’ 关键字保护,保证了互斥访问。// 这相当于管程的入口队列锁。// 生产者方法:放入数据public synchronized void produce(int value) throws InterruptedException {// MESA风格:必须用while,而不是ifwhile (count == buffer.length) {// 缓冲区满了,生产者需要在“满”这个条件上等待// wait() 会释放当前对象锁(即this的锁),让其他线程可以进入管程this.wait(); // 相当于 wait(buffer_full);}// 缓冲区有空位,生产数据buffer[in] = value;in = (in + 1) % buffer.length; // 循环队列count++;// 生产后,缓冲区肯定至少有一个数据,通知可能正在等待的消费者this.notifyAll(); // 相当于 signal(buffer_empty);// 注意:这里用notifyAll()更安全,它会唤醒所有等待的线程(包括生产者和消费者)// 被唤醒的消费者会竞争锁,成功后会再次检查条件(while循环)}// 消费者方法:取出数据public synchronized int consume() throws InterruptedException {while (count == 0) {// 缓冲区空了,消费者需要在“空”这个条件上等待this.wait(); // 相当于 wait(buffer_empty);}// 缓冲区有数据,消费数据int value = buffer[out];out = (out + 1) % buffer.length;count--;// 消费后,缓冲区肯定至少有一个空位,通知可能正在等待的生产者this.notifyAll(); // 相当于 signal(buffer_full);return value;}
}

代码分析:

  1. 互斥 (synchronized)produce 和 consume 方法都被 synchronized 修饰。这意味着任何一个线程进入这两个方法之一时,都会先获取 this 对象的内在锁(管程锁)。其他线程再想进入这个对象的任何一个 synchronized 方法都会被阻塞,排在该对象的入口等待队列中。这自动实现了“每次只有一个线程在管程内”的规则。

  2. 条件变量与等待 (wait()): Java 中每个对象都有一个内在的条件变量(实际上更像一个集合)。this.wait() 表示:

    • 当前线程释放它持有的 this 锁。

    • 该线程被加入到 this 对象的等待集(Wait Set) 中,进入 WAITING 状态。

  3. 条件变量与通知 (notifyAll())this.notifyAll() 表示:

    • 唤醒正在 this 对象等待集中的所有线程。(notify() 则只唤醒一个,随机选择)。

    • 注意:被唤醒的线程不会立即执行! 它们只是从等待集移到了入口等待队列,状态变为 BLOCKED。它们需要重新竞争 this 锁。当前线程(调用 notifyAll 的线程)会继续持有锁,直到它退出 synchronized 方法(即退出管程)或调用 wait() 主动放弃锁。

  4. while 循环检查条件 (Mesa风格): 这是最关键的一点。线程被唤醒后,条件 count == buffer.length 可能已经不再为真了(比如另一个生产者抢先进入并填满了缓冲区)。所以必须用 while 在醒来后重新检查条件,而不能用 if。这是 Mesa 风格管程的编程范式。

生产者线程和消费者线程的使用:

// 创建一个容量为5的缓冲区(管程)
BoundedBuffer buffer = new BoundedBuffer(5);// 生产者线程
Thread producer = new Thread(() -> {try {for (int i = 0; i < 10; i++) {buffer.produce(i);System.out.println("Produced: " + i);Thread.sleep(100); // 模拟生产耗时}} catch (InterruptedException e) {e.printStackTrace();}
});// 消费者线程
Thread consumer = new Thread(() -> {try {int value;for (int i = 0; i < 10; i++) {value = buffer.consume();System.out.println("Consumed: " + value);Thread.sleep(200); // 模拟消费耗时}} catch (InterruptedException e) {e.printStackTrace();}
});producer.start();
consumer.start();

5. 总结

特性说明在Java中的体现
互斥 (Mutual Exclusion)一次只有一个线程能执行管程中的方法。synchronized 关键字
封装 (Encapsulation)共享数据和操作数据的方法被捆绑在一起。类的私有字段和公有方法
条件变量 (Condition Variables)用于在条件不满足时让线程等待。Object.wait()Object.notify()Object.notifyAll()
等待机制等待时自动释放锁,以便其他线程进入。wait() 会释放 synchronized 获取的锁
编程模型Mesa 风格,使用 while 循环检查条件。必须用 while(condition) { wait(); }

管程通过将复杂的同步操作封装在一个模块内,并通过编译器保证互斥,极大地简化了并发程序的编写,提高了代码的可读性和可靠性。它是现代高级语言(如 Java, C#)中并发编程的基石之一。

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

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

相关文章

日志的实现

目录 日志与策略模式 Log.hpp class LogStrategy基类 class ConsoleLogStrategy派生类 classFileLogStrategy派生类 日志等级 获得时间戳 localtime_r函数详解 函数原型 struct tm结构的指针 Logger类(重点) class LogMessage 日志信息类 std::stringstream 用法 重…

【论文阅读】Sparse4D v2:Recurrent Temporal Fusion with Sparse Model

标题&#xff1a; Sparse4D v2&#xff1a;Recurrent Temporal Fusion with Sparse Model 作者&#xff1a; Xuewu Lin, Tianwei Lin, Zixiang Pei, Lichao Huang, Zhizhong Su motivation 在v1的基础上&#xff0c;作者发现长时序有更好的效果&#xff0c;但v1的计算量太大&am…

构建免费的音视频转文字工具:支持多语言的语音识别项目

在当今数字时代&#xff0c;音视频内容越来越多&#xff0c;但如何快速将其转换为文字一直是一个挑战。本项目提供了一个免费的解决方案&#xff0c;支持将视频和音频文件转换为文字&#xff0c;并且支持多语言识别。 一个支持中英文的音视频转文字工具&#xff0c;集成了 Vos…

【开题答辩全过程】以 基于SpringBootVue的智能敬老院管理系统为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

Linux 830 shell:expect,ss -ant ,while IFS=read -r line,

[rootsamba caozx26]# scp /home/caozx26/pub root192.168.235.3:~/ root192.168.235.3s password: /home/caozx26/pub: not a regular file [rootsamba caozx26]# ls app km nntp.sh ntp.sh until1.sh 公共 图片 音乐 find.sh l2 ntp1.sh pub u…

​​​​​​​GPT-5发布引爆争议,奥特曼连夜回应!付费充值的Plus用户成最大赢家?

摘要&#xff1a; GPT-5发布后&#xff0c;社区口碑两极分化&#xff0c;从“强无敌”到“还我4o”的呼声并存。面对技术故障和用户质疑&#xff0c;OpenAI CEO萨姆奥尔特曼及团队火速回应&#xff0c;公布了一系列补救措施和未来计划。本文将带你速览这场风波始末&#xff0c;…

Python 操作 Redis 的客户端 - Redis Stream

Python 操作 Redis 的客户端 - Redis Stream1. Redis Stream2. Redis Commands2.1. CoreCommands.xadd() (生产端)2.2. CoreCommands.xlen() (生产端)2.3. CoreCommands.xdel() (生产端)2.4. CoreCommands.xrange() (生产端)2.5. RedisClusterCommands.delete()3. Redis Stream…

【Qt开发】按钮类控件(一)-> QPushButton

目录 1 -> 什么是 PushButton&#xff1f; 2 -> 相关属性 3 -> 代码示例 3.1 -> 带有图标的按钮 3.2 -> 带有快捷键的按钮 4 -> 总结 1 -> 什么是 PushButton&#xff1f; 在 Qt 框架中&#xff0c;QPushButton 是最基础且最常用的按钮控件之一&am…

Citrix 零日漏洞自五月起遭积极利用

安全研究员 Kevin Beaumont 披露了有关 CVE-2025-6543 的惊人细节&#xff0c;这是一个严重的 Citrix NetScaler 漏洞&#xff0c;在该公司发布补丁之前的几个月里&#xff0c;该漏洞被积极利用作为零日攻击。 Citrix 最初将其轻描淡写为简单的“拒绝服务”漏洞&#xff0c;但…

【系列08】端侧AI:构建与部署高效的本地化AI模型 第7章:架构设计与高效算子

第7章&#xff1a;架构设计与高效算子 要将AI模型成功部署到端侧&#xff0c;除了对现有模型进行压缩和优化&#xff0c;更根本的方法是在设计之初就考虑其在资源受限环境下的运行效率。本章将深入探讨如何设计高效的网络架构&#xff0c;以及如何理解并优化常用的核心算子。高…

42-Ansible-Inventory

文章目录Ansible基本概述手动运维时代&#xff08;原始社会&#xff09;自动化运维时代自动化运维工具的优势Ansible的功能及优点Ansible的架构Ansible的执行流程安装AnsibleAnsible配置文件生效顺序Ansible inventory主机清单Ansible基于免秘钥方式管理客户端小结Ansible-Adho…

Go语言runtime/trace工具全面解析

基本概念与功能 Go语言的runtime/trace是Go标准库中内置的性能分析工具,主要用于追踪和可视化Go程序的运行时行为。它能够记录程序执行期间的各种事件,包括goroutine调度、系统调用、垃圾回收(GC)、网络I/O、锁等待等关键信息。 trace工具的核心功能包括: goroutine生命周期…

Docker(自写)

Docker程序是跑在操作系统上的&#xff0c;而操作系统上又装了各种不同版本的依赖库和配置程序依赖环境&#xff0c;环境不同&#xff0c;程序就可能跑不起来&#xff0c;如果我们能将环境和程序一起打包docker就是可以将程序和环境一起打包并运行的工具软件基础镜像DockerFile…

深度拆解 OpenHarmony 位置服务子系统:从 GNSS 到分布式协同定位的全链路实战

1. 系统概述 OpenHarmony 的“定位子系统”就是硬件服务子系统集里的 “位置服务子系统”(Location SubSystem)。它向下对接 GNSS/GPS、基站、Wi-Fi 等定位模组,向上以 标准位置 API 形式为应用提供 实时位置、轨迹、地理围栏 等能力,并可与分布式软总线联动,实现 跨设备…

React Native基本用法

1&#xff0c;index调用registerComponent,把appName注入到React Native的根节点。 2&#xff0c;package.json是全局大管家&#xff0c;package-lock.json锁定版本&#xff0c;不会手动编辑&#xff0c;通过install安装 3&#xff0c; bebal.config.json bebal.config.json是翻…

LoraConfig target modules加入embed_tokens(64)

LoraConfig target modules加入embed_tokens 更好且成本更低的方法 嵌入层(embedding layer)的 lora_embedding_A 和 lora_embedding_B 头部(head)是否需加入目标模块列表 用户警告 解除权重绑定 解绑以后是随机权重,怎么办 更好且成本更低的方法 “有没有一种更好且成本…

笔记共享平台|基于Java+vue的读书笔记共享平台系统(源码+数据库+文档)

笔记共享平台|读书笔记共享平台系统 目录 基于Javavue的读书笔记共享平台系统 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff…

【VSCode】VSCode为Java C/S项目添加图形用户界面

为Java C/S项目添加图形用户界面 现在我们来为它添加图形用户界面(GUI)。我将使用Java Swing库创建一个简单的GUI&#xff0c;因为它内置于Java标准库中&#xff0c;无需额外依赖。 客户端GUI实现 首先&#xff0c;我们将修改客户端代码&#xff0c;添加一个Swing GUI界面&…

【云原生】Docker 搭建Kafka服务两种方式实战操作详解

目录 一、前言 二、Docker 搭建kafka介绍 2.1 Docker 命令部署 2.2 使用Docker Compose 部署 2.3 使用 Docker Swarm 2.4 使用 Kubernetes 2.5 部署建议 三、Docker 搭建kafka操作方式一 3.1 前置准备 3.2 完整操作过程 3.2.1 创建docker网络 3.2.2 启动zookeeper容…

DBeaver中禁用PostgreSQL SSL的配置指南

在DBeaver中为PostgreSQL连接禁用SSL是一个常见的配置&#xff0c;特别是当你的数据库服务器未启用SSL或遇到连接问题时。我来为你详细讲解操作步骤和注意事项。 &#x1f6e0;️ DBeaver中禁用PostgreSQL SSL的配置指南 详细步骤 打开驱动设置&#xff1a;在DBeaver中创建新的…