PV基础
cpp
typedef struct {
int value;
struct process* l;//等待队列
}semaphore;
void wait(semaphore s){// P()
s.value--;
if(s.value<0) {
add this process to s.l
block(s.l)//block原语使进程从运行态->阻塞态
}
}
void signal(semaphore s){// V()
s.value++;
if(s.value<=0) {
remove a process p from s.l
wakeup(p)//唤醒等待队列的一个进程
}
}

本质其实就是实现两个操作
1.互斥访问
cpp
semaphore mutex=1;
p(){
while(1){
wait(mutex)
op
signal(mutex)
}
}
2.先A后B
cpp
semaphore mutex=0;
A(){
while(1){
op
signal(mutex) //mutex++
}
}
B(){
while(1){
wait(mutex) //mutex--,实现加完才能减
op
}
}
经典同步问题
生产者消费者问题
1.实现缓冲区互斥访问 mutex=1
2.先生产再消费 empty=n full=0
cpp
semaphore mutex=1
semaphore empty=n,full=0
producer(){
while(1){
wait(empty)
wait(mutex)
放入缓冲区
signal(mutex)
signal(full)
}
}
consumer(){
while(1){
wait(full)
wait(mutex)
从缓冲区取出
signal(mutex)
signal(empty)
}
}
读者 - 写者问题
1.允许多个读者同时读取资源。
2.确保当有一个写者访问资源时,没有其他读者或写者可以同时访问。
cpp
int count=0;//在读数
semaphore mutex=1;//实现count互斥访问
semaphore rw=1;//实现写时只能一个写
writer(){
while(1){
wait(rw)
写
signal(rw)
}
}
reader(){
while(1){
wait(mutex)
if(count==0) p(rw);//没人读时,修改信号不可写
count++
signal(mutex)
读
wait(mutex)
count--;//读完释放
if(count==0) v(rw);//没人在读时,修改信号可写
signal(mutex)
}
}
但是上面代码有一个问题就是,只要有人在读就写不了,可以引入一个信号量实现写者公平排队
cpp
int count = 0; // 当前正在读的读者数
semaphore mutex = 1; // 保护 count 的互斥量
semaphore rw = 1; // 控制对共享数据的读写互斥(写者独占,读者并发)
semaphore w = 1; // 用于给写者"优先权"的门
writer() {
while (1) {
wait(w); // 申请"门"以表明有写者想写(阻止新读者进入)
wait(rw); // 等待没有读者或写者在占用(真正进入写区)
// 写操作(独占访问共享数据)
signal(rw); // 写完释放对共享数据的占用
signal(w); // 放弃"门",允许其他读者/写者继续
}
}
reader() {
while (1) {
wait(w); // 等待门,若有写者已取得门,则新读者在此阻塞
wait(mutex); // 保护对 count 的访问
if (count == 0) // 第一个读者阻塞写者(占用 rw)
wait(rw);
count++;
signal(mutex);
signal(w); // 立即释放门,允许写者/读者竞争(但若有写者已在门外等待,则门会被写者占用)
// 读操作(可以与其他读者并发)
wait(mutex);
count--;
if (count == 0) // 最后一个读者释放对写的阻塞
signal(rw);
signal(mutex);
}
}
哲学家就餐问题
解决死锁问题
1.允许进餐人数-1
cpp
//五个人至多四个进餐
semaphore count=4;
semaphore chopsticks[4] = {1,1,1,1};
philospher(){
while(1){
wait(count)
wait(chopsticks[i])
wait(chopsticks[(i+1)%5])
eat
signal(chopsticks[i])
signal(chopsticks[(i+1)%5])
signal(count)
}
}
2.左右筷子都有时才拿(一次拿走想要的所有资源
cpp
//五个人至多四个进餐
semaphore mutex=1;
semaphore chopsticks[5] = {1,1,1,1,1};
philospher(){
while(1){
wait(mutex)
wait(chopsticks[i])
wait(chopsticks[(i+1)%5])
signal(mutex)
eat......
signal(chopsticks[i])
signal(chopsticks[(i+1)%5])
}
}
还有一种写法是序号奇的先拿左边,偶的先拿右边,代码无非加个if判断就不写了
真题
09

这题不同的地方在于取奇数和偶数,因此需要信号量判断有没有奇数/偶数存在,把原来的full改成odd和even即可。
具体函数如何实现统计个数不用操心,调用题面给的函数就行。
cpp
semaphore mutex=1;//实现缓冲区互斥访问
semaphore empty=n;
semaphore odd=0,even=0;
P1(){
while(1){
produce(a)
wait(empty)
wait(mutex)
put(a)//放入缓冲区
signal(mutex)
if(a%2) signal(even)
else signal(odd)
}
}
P2(){
while(1){
wait(odd)
wait(mutex)
getodd()//取出奇数
signal(mutex)
signal(empty)
countodd()
}
}
P2(){
while(1){
wait(even)
wait(mutex)
geteven()//取出奇数
signal(mutex)
signal(empty)
counteven()
}
}
11

cpp
1.取号机互斥访问
2.有空才能取
3.营业员空闲叫号
semaphore mutex=1;//实现取号机互斥访问
semaphore empty=10,full=0;
semaphore service=0;//实现营业员叫号后顾客才能获取服务
cobegin
{
process 顾客 i
{
wait(empty)
wait(mutex)
从取号机获得一个号码;
signal(mutex)
signal(full)
wait(service)
等待叫号;
获得服务;
}
process 营业员
{
while (TRUE) {
wait(full)
叫号;
signal(empty)//释放座位
signal(service)
为顾客服务;
}
}
}coend
13

最简单一题说是,仔细就行
cpp
最多可以容纳 500 人同时参观,有一个出入口,
该出入口一次仅允许一个人通过。
semaphore empty=500;
semaphore mutex=1;
cobegin
参观者进程 i:
{
wait(empty)
wait(mutex)
进门;
signal(mutex)
参观;
wait(mutex)
出门;
wait(mutex)
signal(empty)
}
coend
14

这题的点主要在于:连续取出 10 件产品后,其他消费者进程才可以取产品。
也就是要把一个顾客连续取十件产品维护成一个原子操作,单独开一个mutex用来实现互斥,在里面for取产品即可,有点像生产者消费者问题里面一次取出所有需要的资源。
cpp
1000 件产品的环形缓冲区(初始为空)
当缓冲区未满时,生产者进程可以放入其生产的一件产品,否则等待;
当缓冲区未空时,消费者进程可以从缓冲区取走一件产品,否则等待。
连续取出 10 件产品后,其他消费者进程才可以取产品。
semaphore empty=1000;
semaphore mutex=1;//实现缓冲区互斥访问
semaphore fmutex=1;//实现顾客互斥访问
semaphore full=0;
producer(){
while(1){
生产
wait(empty)
wait(mutex)
放入缓冲区
signal(mutex)
signal(full)
}
}
consumer(){
while(1){
wait(fmutex)//实现消费者互斥
for(int i=1;i<=10;i++){
wait(full)
wait(mutex)
取产品
siganl(mutex)
signal(empty)
}
signal(fmutex)
}
}
15

放心大胆用semaphore就行,看了下真题好像没用int的。
定义空位和满座位,模拟即可。
cpp
a初始x最多m,b初始y最多n
从自己的信箱中取得对方的问题,
将一个邮件放入对方的信箱中。
semaphore A_full = x; // A 信箱中已有的邮件个数
semaphore A_empty = M - x; // A 信箱还可以放多少个邮件
semaphore B_full = y; // B 信箱中已有的邮件个数
semaphore B_empty = N - y; // B 信箱中还能放多少个邮件
semaphore A_mutex = 1; // 互斥访问 A 信箱
semaphore B_mutex = 1; // 互斥访问 B 信箱
A() {
while (1) {
P(A_full);
P(A_mutex);
从A信箱中取出一个邮件;
V(A_mutex);
V(A_empty);
回答问题并提出新问题;
P(B_empty);
P(B_mutex);
将信件放入B邮箱;
V(B_mutex);
V(B_full);
}
}
B() {
while (1) {
P(B_full);
P(B_mutex);
从B信箱中取出一个邮件;
V(B_mutex);
V(B_empty);
回答问题并提出新问题;
P(A_empty);
P(A_mutex);
将信件放入A邮箱;
V(A_mutex);
V(A_full);
}
}
19

题目"尽可能多的哲学家用餐"指的是:在不产生死锁的前提下,使能同时进餐的哲学家数量最大化 。
这里的最大值 并不是 n ,而是 n − 1。
为什么不是 n?因为 所有 n 个哲学家同时拿起左筷子,会导致必然死锁 。
要避免死锁,系统必须在某个时刻 至少有 1 个哲学家不能拿起筷子 ,因此最大可同时进入"尝试就餐区"的哲学家只能是 n − 1。
cpp
//防止死锁且经可能多的用餐
//拿走所有资源再运行
semaphore chopsticks[n]={1};
semaphore plate=min(n-1,m);
philospher(int i){
while(1){
wait(plate)
wait(chopsticks[i])
wait(chopsticks[(i+1)%n])
用餐
signal(chopsticks[i])
signal(chopsticks[(i+1)%n])
signal(plate)
}
}
21

1)信号量 S是能被多个进程共享的变量,多个进程都可通过 wait() 和 signal() 对 S 进行读、写操作。所以,wait() 和 signal() 操作中对 S 的访问必须是互斥的。
2)方法 1 错误。在 wait() 中,当 S≤0 时,关中断后,其他进程无法修改 S 的值,while 语句陷入死循环。方法 2 正确。方法 2 在循环体中有一个开中断操作,这样就可以使 其他进程修改 S 的值,从而避免 while 语句陷入死循环。
3)用户程序不能使用开/关中断指令实现临界区互斥。因为开中断和关中断指令都是特权 指令,不能在用户态下执行,只能在内核态下执行。
22

简单同步关系,要让a在b前执行,开一个信号量在a最后v,b最开始p就行。
cpp
sempahore AC = 0;
sempahore CE = 0;
T1() {
A;
signal(AC);
wait(CE);
E;
F;
}
T2() {
B;
wait(AC);
C;
signal(CE);
D;
}
25

water只有丙会用
甲和是属于只要能挖坑就一直挖,乙是只要能放树苗填土就一直填
但丙的浇水动作不是无脑的,得等填完土才能浇水,所以water用于实现同步,乙做完丙就做。
cpp
三个人一起植树,甲挖坑,乙放树苗入坑并填土,丙负责为新种树苗浇水。
步骤依次为:挖树坑,放树苗,填土和浇水。
现在有铁锹和水桶各一个,铁锹用于挖树坑,填土。
水桶用于浇水。当树坑数量小于 3 时,甲才可以挖树坑。
设初始坑 = 0,铁锹水桶均可用,定义尽可能少的信号量,
semaphore empty=0;//坑数目
semaphore full=3;
semaphore tieqiao=1,water=0;
cobegin() {
甲(){
while(1){
wait(full)
wait(tieqiao)//互斥访问铁锹
挖坑
signal(tieqiao)
signal(empty)
}
}
乙(){
while(1){
wait(empty)
放树苗入坑
wait(tieqiao)//互斥访问铁锹
填土
signal(tieqiao)
signal(full)
signal(water)
}
}
丙(){
while(1){
wait(water)
浇水
}
}
}
写代码比考试快乐多了,即使是伪代码。我讨厌408。