计算机操作系统(十五)互斥锁,信号量机制与整型信号量
前言
- 在上一篇博客中,我们系统剖析了 死锁 的 底层原理与典型场景。
- 作为并发编程中另一核心议题,进程同步机制不仅是规避死锁风险的关键手段,更是保障多进程协同工作正确性的基础框架。
- 本篇将从同步需求的本质出发,深入探讨进程 同步问题,信号到信号量,临界区,信号量的实现与使用
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的操作系统博客专栏
https://blog.csdn.net/2402_83322742/category_12916780.html?spm=1001.2014.3001.5482
一、进程同步问题
1.1 什么是进程?
进程就是计算机里正在运行的程序,比如你同时打开微信、浏览器、Word,每个都是一个进程。
1.2 为什么需要同步?
例子:银行账户取钱
假设你和朋友同时用手机给同一个账户转账100元,理想情况账户应该增加200元,但如果没有"排队"机制,可能出现:
- 你先读取账户余额1000元,朋友同时也读取1000元
- 你转账后余额变为1100元,朋友转账后也写回1100元
- 最终账户只增加了100元,钱"消失"了!
这就是进程同步问题:多个进程访问共享资源时,结果可能出错,必须让它们"有序"操作。
二、从信号到信号量
2.1 什么是信号?
信号是操作系统给进程的"通知",比如按Ctrl+C就是给进程发一个"终止信号"。
- 但信号只能发通知,无法解决复杂的排队问题(比如控制多少人能同时访问资源)。
2.2 信号量的诞生
信号量就像"钥匙管理员",它做两件事:
- 记录当前可用资源的数量(比如有3把钥匙)
- 控制进程能否获取资源(拿到钥匙才能进门)
例子:图书馆自习室
自习室有10个座位(资源数量),信号量就像登记本,记录当前空座位数。想进自习室的人先看登记本:
- 有空位:拿走一个座位(登记本减1),进去学习
- 没空位:在门口等待,直到有人离开(登记本加1时被通知)
三、临界区:不能多人同时进的"小房间"
3.1 什么是临界区?
临界区是一段代码,这段代码访问共享资源(比如变量、文件)时,必须独占访问。就像厕所,一次只能进一个人,否则会尴尬(出错)。
3.2 临界区的规则
- 互斥性:同一时间只能一个进程在临界区
- 进入有限等待:不能让进程永远等在外面
- 有空让进:临界区空了要允许进程进入
3.3 为什么需要临界区?
回到银行转账例子,"读取余额→修改余额→写回余额"这一段就是临界区,必须保证同一时间只有一个进程执行,否则数据会错乱。
四、信号量的实现与使用
4.1 信号量的核心操作
信号量S是一个整数变量,配合两个操作:
- P(S):申请资源(Wait操作),相当于"我要拿钥匙"
- 如果S>0:S减1,成功进入
- 如果S=0:阻塞等待,直到有人释放资源
- V(S):释放资源(Signal操作),相当于"还钥匙"
- S加1,唤醒等待的进程
4.2 用信号量实现互斥(二元信号量)
当信号量S初始化为1时,它就变成了"互斥锁":
// 伪代码:两个进程互斥访问临界区
Semaphore S = 1; // 初始有1把钥匙
进程A: 进程B:
P(S); P(S); // 都想拿钥匙
临界区代码; 临界区代码;
V(S); V(S); // 用完还钥匙
原理:S=1时,第一个进程P(S)后S=0,第二个进程P(S)时发现S=0,只能等待;第一个进程V(S)后S=1,第二个进程才能进入。
4.3 用信号量实现同步(计数信号量)
当信号量S初始化为N(N>1)时,它可以控制N个进程同时访问资源:
例子:打印机共享(3台打印机,5个进程使用)
Semaphore printer = 3; // 初始3把钥匙
进程1: 进程2: ... 进程5:
P(printer); P(printer);
使用打印机; 使用打印机;
V(printer); V(printer);
原理:前3个进程P(printer)后,printer从3减到0,第4、5个进程P时会阻塞,直到前3个进程中有人用完还钥匙(V操作)。
五、经典同步问题
5.1 生产者-消费者问题:
问题描述:生产者做面包放到货架,消费者从货架拿面包,货架容量有限(比如只能放10个面包)。
关键点:
- 互斥:同一时间只能一人访问货架(防止面包被抢)
- 同步:货架满时生产者等待,货架空时消费者等待
信号量解法:
Semaphore empty = 10; // 空位置数量
Semaphore full = 0; // 满位置数量(初始没面包)
Semaphore mutex = 1; // 互斥访问货架
生产者: 消费者:
P(empty); P(full);
P(mutex); P(mutex);
放面包到货架; 从货架拿面包;
V(mutex); V(mutex);
V(full); V(empty);
逻辑:生产者先看有没有空位置(empty),再拿互斥锁放面包;消费者先看有没有面包(full),再拿互斥锁拿面包。
5.2 读者-写者问题:
问题描述:多个读者可以同时看书,但写者必须独占写书,且写时不能有读者和其他写者。
关键点:读者优先(或写者优先),需要记录读者数量,保证第一个读者来加锁,最后一个读者走解锁。
信号量解法(读者优先):
Semaphore rwlock = 1; // 读写互斥锁
Semaphore mutex = 1; // 保护读者计数器的互斥锁
int readcount = 0; // 读者数量
读者: 写者:
P(mutex); P(rwlock);
readcount++; 写数据;
if(readcount == 1) P(rwlock); // 第一个读者加锁
V(mutex); V(rwlock);
读数据;
P(mutex);
readcount--;
if(readcount == 0) V(rwlock); // 最后一个读者解锁
V(mutex);
逻辑:读者来先增加计数器,第一个读者加锁;写者直接加锁,保证独占。
5.3 哲学家就餐问题:
问题描述:5个哲学家围坐一桌,每两人之间有一根筷子,哲学家只能同时拿左右两根筷子才能吃饭,否则思考。
关键点:避免死锁(比如所有人都拿左边筷子,然后等右边筷子,永远吃不上饭)。
避免死锁的解法:
- 最多允许4个哲学家同时拿筷子(总有一个能吃到)
- 规定奇数哲学家先拿左筷子,偶数先拿右筷子(打破循环等待)
信号量解法(方案1):
Semaphore chopstick[5] = {1,1,1,1,1}; // 5根筷子
Semaphore count = 4; // 最多4人拿筷子
哲学家i:
P(count); // 先申请"就餐资格"
P(chopstick[i]); // 拿左筷子
P(chopstick[(i+1)%5]); // 拿右筷子
吃饭;
V(chopstick[(i+1)%5]);
V(chopstick[i]);
V(count); // 释放就餐资格
逻辑:限制同时拿筷子的人数为4,保证至少有一个哲学家能拿到两根筷子。
以上就是对本次关于操作系统博客内容的总结,后续我们将深入探讨操作系统更多知识。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的操作系统博客专栏
https://blog.csdn.net/2402_83322742/category_12916780.html?spm=1001.2014.3001.5482
非常感谢您的阅读,喜欢的话记得三连哦 |