🧨🧨🧨🧨🧨🧨🧨🧨大年初三篇🧨🧨🧨🧨🧨🧨🧨🧨

🎉❤️🎉❤️🎉❤️🎉❤️🎉❤️祝各位程序员们马年大吉 万事如意!❤️🎉❤️🎉❤️🎉❤️🎉❤️🎉
目录
[从生活中的例子理解: 放映厅的座位数](#从生活中的例子理解: 放映厅的座位数)
[二元信号量 ≈ 锁](#二元信号量 ≈ 锁)
[x64 msvc v19.latest](#x64 msvc v19.latest)
[x86-64 gcc 15.2](#x86-64 gcc 15.2)
[x86-64 clang 21.1.0](#x86-64 clang 21.1.0)
[struct sembuf](#struct sembuf)
1.回顾共享内存的缺点
之前在OS53.【Linux】System V 共享内存(2)文章讲过共享内存的缺点: 无保护机制
例子说明问题
例如进程A和进程B使用共享内存通信,进程A向正在共享内存写入,进程B就过来读取,然后进程B取消挂接共享内存**,导致进程B没有读取完整的数据**
引发的问题: 双方的发送和接受的数据不完整,即数据不一致的问题
其他非同步互斥的现象
多进程、多线程并发打印,显示器上的消息:
-
错乱的
-
混乱的
-
和命令行混在一起
原因:显示器也是文件,多进程、多线程向显示器文件写入,显然多进程、多线程都看到了同一个显示器资源,即共享资源,但是显示器资源没有同步互斥保护机制,即数据不一致问题
解决方法
解决方法: 加锁,这样能互斥访问,换句话说,++任何时刻,只允许一个执行流访问共享资源,叫做互斥访问++
例如,进程A在读或写共享内存时,进程B无法读或写共享内存,此时只允许进程A访问共享内存,反之亦然
生活中互斥访问的例子
ATM取钱
临界资源和临界区的定义
临界资源: 任何时刻只允许一个执行流访问(就是执行访问代码)的共享资源
**临界区:**临界区说的不是代码所在的地方,它是一个抽象的概念,
比如要执行的这几行代码在访问临界资源,那么这几行代码执行的区间,就是临界区,这个区间一般是在栈上的,因为代码是在函数栈帧中被执行的
那么任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源
2.理解信号量
信号量(semaphore)在有些旧的教材中被称为信号灯,其**++本质是一把计数器++** ,类似int cnt=n(不可等同 ),++描述临界资源的多少++
从生活中的例子理解: 放映厅的座位数
假设一个放映厅一共100个座位,那么电影院最多能卖这个放映厅的100张票
观众未看电影时,先去买票,可以得出买票的本质: 对资源的预订机制
无特殊情况下,A买过票后,放映电影的这个时间段内,座位就是A的,即使A不去
进一步得出: 电影院的售票机中一定设置了票数计数器,每当一个观众成功买票后,计数器就要减1,放映厅里面的资源就少1个
当票数的计数器到减到0之后,资源已经被申请完毕了
回到信号量理解
临界资源可以被分成很多份小资源,某个执行流可能需要访问临界资源其中的几份小资源,那么操作系统没有必要将整个临界资源全部保护起来,只需要保护该执行流占用的那几份小资源
这样其他执行流就能占用剩下的小资源这就形成了多执行流同时访问临界资源,例如多进程并发访问临界资源
最怕的情况
最怕的情况: 多执行流同时访问同一份资源,假设一个执行流访问一份资源,现有n个资源,放进来n+以上的执行流
解决方法
引入一个计数器(信号量),每申请一份资源,计数器就减1,知道计数器为0,无法再申请资源,这样就不会出现多执行流同时访问同一份资源
总结
-
申请计数器成功,就表示具有访问资源的权限了
-
申请了计数器资源.当前不一定访问资源,即申请是资源的预订机制
-
计数器可以有效保证进入共享资源的执行流的数量
-
所以每一个执行流,想访问共享资源中的一部分的时候.不是直接访问,而是先申请计数器资源,类比看电影的先买票
计算机科学中,把这个"计数器"称为信号量
二元信号量 ≈ 锁
如果电影院的放映厅里只有一个座位(资源为1 ),那么只需要一个数值为1的计数器,显然最多只有一个人能买到票(只有一个执行流在访问临界资源,即互斥),而且计数器的值要么为0要么为1,只有两种情况
二元信号量: 值只能为0或1两态的信号量,本质是一个锁,有互斥功能
什么情况下资源为1
临界资源不分成很多块,而是当做一个整体→整体申请、整体释放
信号量计数器本身也是共享资源
执行流要访问临界资源,需先向计数器(信号量)申请资源,那么信号量计数器本身也是共享资源 ,那么要保证申请资源不出问题,首先要++保护信号量计数器的安全++
执行流申请到一份资源,计数器就减1,但是"减1"本身是不安全的
"减1"本身是不安全的
上面说过"信号量(semaphore)在有些旧的教材中被称为信号灯,其**++本质是一把计数器++** ,类似int cnt=n,++描述临界资源的多少++"
cpp
cnt--;
虽然在C语言上这是一条语句,但是从指令的角度上,这个会被编译器做成多条指令处理,例如:
x64 msvc v19.latest

x86-64 gcc 15.2

x86-64 clang 21.1.0

"减1"会被编译器拆成多条指令执行,而且进程在运行的时候可以随时被切换,可能会导致cnt的值出问题,暂时不深究
简单理解P操作、V操作和PV操作
1.申请信号量,本质是对计数器做减1操作,即P操作
2.释放资源,释放信号量,本质是对计数器进行加1操作,即V操作
3.申请和释放称为PV操作,且操作是原子的
"原子的"含义
1.要么未做,要么做完,没有"正在做"这个概念,换句话说任何执行流都看不到其他执行流正在做减1或加1操作
2.从技术角度,只用一条汇编指令,就是原子的
这就解释了"++保护信号量计数器的安全++"
简单了解与信号量相关的系统调用
semget

nsems表示申请信号量的个数,注意: 多个信号量 和 信号量是几 概念不一样
semctl

可用于删除信号量或初始化信号量
semop

可用于设置信号量
struct sembuf
semop的第二个参数是struct sembuf的指针,struct sembuf需要自己定义
手册上指出应该有这些字段:
cpp
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
sem_num指定操作哪一个信号量,相当于数组元素下标
信号量是进程间通信的一种
信号量是进程间通信的一种,那么信号量首先要被所有的通信进程看到
理解: 通信的不仅仅是通信数据,互相协同也是通信
协同: 只要A的局部状态变化能让B据此改变自己的行为,就必然发生了"信息"的传递,也就落入"通信"的范畴