开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情 这也是第25篇文章
哲学家就餐问题(互斥访问有限资源的竞争问题)
问题描述
一群哲学家围在圆桌前,不是思考就是吃饭。他们每人左手边放着一把刀,右手边放着一把叉(有的模型也说筷子之类)。当一个哲学家开始吃饭的时候,他得首先拿起右边的叉,然后拿起左边的刀。如果他拿起了叉,但得不到刀,就又会放下刀,等候一会儿后再次尝试。
- 问题:当所有哲学家都同时拿起右边的叉时,当他们都想拿起左边的刀就都会失败;当他们都放下右边的叉并再次尝试时,相同的情况还是会发生。最终都忙着拿叉放叉,但吃不上饭。
- 一个解决思路:关键在于“等候时间”,只要让他们等待的不等(错开)就可以了。
- 隐患:但还是不能保证绝对没有意外,,而有些情况是绝对不能容许意外的,比如核电站管理。
信号量解法
#define N 5
#define LEFT (i+N-1)%N
#define RIGHT (i+1)%N
#define THINKING 0
#define HUNGRY 1
#define EATING 2
#define TRUE 1
typedef int semaphore;
int state[N];//每位哲学家的状态
semaphore mutex=1;//临界区的互斥
semaphore s[N];//每个哲学家一个信号量
void philosopher(int i){
while(TRUE) ;//一直坐在桌边
}
void take_forks(int i){
down(&mutex);//进入临界区
state[i]=HUNGRY;
test(i);//尝试获取两把叉子
up(&mutex);//离开临界区
down(&s[i]);//如果得不到叉子会阻塞
}
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]);//和take_forks里的down(&s[i])对应
}
}
读者-写者问题
问题描述
该问题常被用于数据库建模。多个读者可以同时访问数据内容,但是当有写者在写入的时候,读者访问到的数据是不准确的(由此引出了脏读,幻读,不可重复读等问题),所以数据库课程告诉我们,有写者访问时应该上锁,访问完了再解除锁。
- 问题:上述只是最理想最简单的情况,但实际情况远要复杂得多,而且在高并发、大数据之类的场景下,频繁加锁是性能极低且不现实的。
- 解决方案:有一个同样很基础的、原理性的方案,是用信号量。虽然同样是实现互斥,但是开销比实现锁要小。
代码
以下是一个读者优先的方案。(不过也有可能造成写者饥饿,稍加修改可改写为写者优先)
#define TRUE 1
typedef int semaphore;
semaphore mutex=1;//互斥访问"读进程数"
semaphore db=1;
int rc=0;//读进程数
void reader(){
while(TRUE){
down(&mutex);
rc+=1;
if(rc==1) down(&db);//第一个读者对db down,其他只是改变rc
up(&mutex);//释放对rc的互斥访问
read_data_base();
down(&mutex);
rc-=1;
if(rc==0) up(&db);//尾进程up db,首位呼应,便于写进程重获db
up(&mutex);
use_data_read();
}
}
void writer(){
while(TRUE){
think_up_data();
down(&db); //如果一个进程在写db,其他进程都不能访问该进程
write_data_base();
up(&db);
}
}