No43.进程间的同步与互斥---哲学家问题
2019-12-22 本文已影响0人
赫尔特
文章目录
1.水果问题
- 桌上有一只盘子,每次只能放一个水果,而且盘子最多装一个水果,爸爸只往里面放苹果,妈妈只往里面放香蕉,儿子只吃香蕉,女儿只吃苹果,用P、V操作实现四人正确活动的程序。
semaphore empty=1; //判断盘子是否为空的信号量
semaphore apple=0,banana=0; //是否有苹果,香蕉的信号量
Father:
P(empty)
向盘子里放苹果;
V(apple);
Mother:
P(empty)
向盘子里放香蕉;
V(banana);
Daughter:
P(apple)
取盘子里的苹果;
V(empty);
Son:
P(banana)
取盘子里的香蕉;
V(empty);
- 桌上有一只盘子,每次只能放和取出一个水果,而且盘子最多装2个水果,爸爸只往里面放苹果,妈妈只往里面放香蕉,两个儿子只吃香蕉,两个女儿只吃苹果,用P、V操作实现六人正确活动的程序。
用一个信号量对盘子里的水果个数进行计数,用另一个信号量用来控制对盘子的互斥访问(因为每次只能放和取出一个水果),还有苹果和香蕉的信号量
semaphore empty=2; //对盘子里的水果个数进行计数
semaphore mutex=1; //控制对盘子的互斥访问
semaphore apple=0,banana=0; //是否有苹果,香蕉的信号量
Father:
P(empty);
P(mutex);
向盘子里放苹果;
V(mutex);
v(apple);
Mother:
P(empty);
P(mutex);
向盘子里放香蕉;
V(mutex);
v(banana);
Daughter i(i=1,2):
P(apple);
P(mutex);
取盘子里的苹果;
V(mutex);
v(empty);
Son i(i=1,2):
P(banana);
P(mutex);
取盘子里的香蕉;
V(mutex);
v(empty);
2.公交车问题
- 设公交车上有一位司机和一位售票员,它们的活动如下:
graph LR
A[司机] --> B[启动车辆]
B --> C[正常行驶]
C--> D[到站停车]
E[售票员] --> F[关车门]
F --> G[售票]
G--> H[开车门]
规定:只有当售票员关车门后司机才能启动车辆,只有当司机停车门后售票员才能开车门。用P、V操作实现两人正确活动的程序。
semaphore start=0; //可以开车的信号量
semaphore open=0; //可以开门的信号量
司机:
P(start);
启动车辆;
正常行驶;
到站停车;
V(open);
售票员:
关车门;
V(start); //因为只有关车门后才能启动车辆
售票;
P(open);
开车门;
3.哲学家问题
- 哲学家问题:
五个哲学家围在一个圆桌旁边吃饭,每相邻两个哲学家之间有且只有1个叉子可以使用。规定:哲学家都是很讲究的,必须抓起自己旁边的两个叉子才会吃饭,否则就算饿死也不吃,哲学家不吃的时候都在思考哲学问题(我是谁?我和动物的区别是什么?我和机器的区别是什么.......),当然,哲学家也不能抓着叉子就不放,吃饭的时间是有限的。怎样才能让我们的哲学家都能吃到饭,比如五个人同时抓起一个叉子,那么五个人都没得吃。
下面这个代码就会产生五个人都没得吃的情况:
take_fork()和put_fork()函数内部都有P,V操作,也就是说同一个叉子不能被两个人同时拿起
#define N 5
void philosopher(int i) //第i个哲学家
{
while(true){
think(); //我要思考问题!
take_fork(); //准备要吃饭了,拿起第i个叉子,
//假设哲学家被编好号,这个i就是哲学家的编号,
//它对应的是抓起左边的叉子
take_fork((i+1)%N); //抓起右边的叉子
eat(); //我开动了
put_fork(i); //放下左边的叉子
put_fork((i+1)%N); //放下右边的叉子
}
}
解决办法有很多,比如下面四种都可行:
1.同一时间只允许四个哲学家同时拿起叉子
2.规定拿起第一个叉子时,编号为奇数的哲学家先拿起自己左边的叉子,而编号为偶数的哲学家先拿起自己右边的叉子
3.更暴力一些,把上面代码think()后面的五句保护起来,设置mutex,规定一次只允许一个哲学家吃饭。
4.规定:当且仅当旁边的两个叉子都可以用时,才允许哲学家拿叉子,否则就一直等待。
下面代码是对第4种方法的实现:
#define N 5 //哲学家的数量
#define LEFT (i+N-1)%N //哲学家i的左边哲学家编号
#define RIGHT (i+1)%N //哲学家i的右边哲学家编号
//下面三个宏都用来表示哲学家的状态
#define THINKING 0 //思考中的哲学家
#define HUNGRY 1 //饿肚子的哲学家
#define EATING 2 //进食中的哲学家
typedef int semaphore;
int state[N]; //记录哲学家现在的状态,这个状态就是前面的三个宏
semaphore mutex=1;
semaphore s[N]; //为每个哲学家设置一个信号量
void philosopher(int i) //i是哲学家编号
{
while(true)
{
think();
take_forks(i);
eat();
put_forks(i);
}
}
void take_forks(int i)
{
down(&mutex);
state[i]=HUNGRY;
test(i); //尝试一下能不能同时拿起左右两只叉子
up(&mutex);
down(&s[i]); //如果在之前的test里不能拿起两个叉子,那么会在这条语句被锁住
}
void put_forks(int i)
{
down(&mutex);
state[i]=THINKING;
test(LEFT); //看看左边的哲学家有没有食欲
test(RIGHT); //看看右边的哲学家有没有食欲
up(&mutex);
}
void test(int i)
{
if(state[i[==HUNGRY && state[LEFT]!=EATING && state[RIGHT]!=EATING)
{
state[i]=EATING;
up(&s[i]);
}
}
4.读者写者问题
- 读者和写者问题
读者和写者都可以访问一个数据库,读者只能读,写者只能写,规定如下:
1.允许任意多个读者同时读数据
2.同一时间只允许一个写者修改数据
3.写者和读者不能同时访问数据
用P、V操作实现的思路:
(把这个数据库想象成图书馆,方便陈述理解)
1.偏向读者型:
(1)当读者到来时,只要有读者在读数据,或者没有人访问数据,这个新的读者都可以读数据
(2)当写者到来时,只有没人访问数据时才能对数据进行写
2.偏向写者型:
(1)当读者到来时,如果前面有写者在等待进入图书馆,读者就不能进去,当然里面有写者也不能进入
(2)当写者到来时,里面有读者依然不能进入,只有里面没人时才能进。
typedef int semaphore;
semaphore mutex=1; //控制对读者的数量进行加减的信号量
semaphore db=1; //图书馆的信号量,database
int rc=0; //图书馆里读者的数量
void reader()
{
while(true)
{
//进入
down(&mutex);
rc++;
if(rc==1)
down(&db);
up(&mutex);
read_db(); //读取数据
//离开
down(&mutex);
rc--;
if(rc==0)
up(&db);
up(&mutex);
}
}
void writer()
{
while(true)
{
down(&db);
write_db();
up(&db);
}
}
5.总结
- P、V操作必须成对出现
- 当为互斥操作时,它们同处于同一进程。
当为同步操作时,它们不在同一进程中。 - 一个同步P操作与一个互斥P操作在一起时同步P操作在互斥P操作前。
- 两个V操作的顺序无关紧要。