熟睡的理发师问题(The Sleeping-Baber Problem)是操作系统中关于进程同步的一个经典问题,它涉及了到临界区保护、锁、信号量等方面的知识。在这篇博客中,我将具体讲解这个问题并用pthread库编码解决,相关pthread库的使用可查阅IEEE官方文档,我是传送门~~~。
问题描述:
某个理发店有个瞌睡虫理发师,只要没有顾客,他就会去睡觉;而整个理发店由两个区域组成:有N把椅子的等候区和一把椅子的理发区。
每当有一个顾客到来时,他有以下三种选择:
1. 理发师在睡觉,则叫醒理发师并开始理发;
2. 理发师在忙但等候区有空位,则进入等候区等候;
3.理发师在忙而等候区又满了,则掉头离开;
现在请你想一个办法帮助协调理发师和顾客。
要解决这个问题,我们先分析一下这个问题中将存在的一些的特殊情况:
1. Deadlock:理发师给一个顾客理完发后没有人通知他后面还有顾客,于是理发师去睡觉而顾客一直等待,形成死锁;
2. Starvation:要是某些顾客来的比较巧,刚好碰到理发师给一个顾客理完发,于是理发师连忙给新来的这个顾客理发而不顾忌等候区的顾客,可能造成等候区的顾客无限的等待下去;
3. Critical Section:多个新来的顾客同时抢占椅子或同时又有一个顾客去理发,这便会涉及到椅子这个资源的分配问题,因此椅子便是这个问题的临界区资源。
在解决Deadlock和Starvation问题时,我们可以通过维护两个信号量(顾客和理发师)来解决,;而Critical Section问题就使用一把互斥锁解决。
// 定义p、v操作
#define p(x) sem_wait(&x)
#define v(x) sem_post(&x)
// 初始化理发师和顾客信号量
sem_t baber, customers;
sem_init(&baber, 0, 1); // 理发师初始为1
sem_init(&customers, 0, 0);// 顾客初始为0
// 互斥信号量 保护临界区(即椅子)
pthread_mutex_t chair_mutex;
所以理发师这边的逻辑大致如下:
p(customers); // 查看顾客等候区(信号量),小于0就去睡觉(线程阻塞)
lock(chair_mutex); // 顾客起身去理发,获得椅子读写的锁
++chair;
unlock(chair_mutex);// 释放锁
// 给顾客理发...
v(baber); // 理完一个,通知等候区的顾客自己已经空闲了
而顾客的逻辑大致如下:
lock(chair_mutex); // 刚到的顾客,获得椅子读写的锁,判断是否还有空位
if(chair > 0) { // 有空位
--chair;
v(customers); // 通知理发师队列有了新顾客
unlock(chair_mutex); // 释放锁
p(baber); // 等待理发师,阻塞线程
// 理发...
// 离开...
} else { // 无空位
unlock(chair_mutex); // 释放锁
// 离开...
}
通过实现以上这两种逻辑,使得理发师在每次睡觉之前将检查一下等候区是否还有顾客在等待(p(customer)),在给一个顾客理完发后又会通知等候区的顾客(v(baber));而每有新的顾客到来时,顾客又将通知理发师(v(customer)),然后去等候区队列的末尾等待理发师(p(baber));同时,使用互斥锁对椅子进行保护,防止了椅子被同时抢占造成资源的不同步。
接下来给出设定一共有20个顾客先后来到,而只有5把椅子的运行结果(完整代码见末尾):
the baber shop opens...
customer #0 comes...
the chair left : 4
customer #0 is getting a hair cut...
the baber is working on one...
so the chair left : 5
the baber has done one!
customer #1 comes...
the chair left : 4
customer #1 is getting a hair cut...
the baber is working on one...
so the chair left : 5
the baber has done one!
customer #2 comes...
the chair left : 4
customer #2 is getting a hair cut...
customer #3 comes...
the chair left : 3
the baber is working on one...
so the chair left : 4
the baber has done one!
customer #3 is getting a hair cut...
customer #4 comes...
the chair left : 3
customer #5 comes...
the chair left : 2
the baber is working on one...
so the chair left : 3
the baber has done one!
customer #4 is getting a hair cut...
customer #6 comes...
the chair left : 2
customer #7 comes...
the chair left : 1
the baber is working on one...
so the chair left : 2
the baber has done one!
customer #5 is getting a hair cut...
customer #8 comes...
the chair left : 1
customer #9 comes...
the chair left : 0
the baber is working on one...
so the chair left : 1
the baber has done one!
customer #6 is getting a hair cut...
customer #10 comes...
the chair left : 0
customer #11 comes...
customer #11 left...
the baber is working on one...
so the chair left : 1
the baber has done one!
customer #7 is getting a hair cut...
customer #12 comes...
the chair left : 0
customer #13 comes...
customer #13 left...
the baber is working on one...
so the chair left : 1
the baber has done one!
customer #8 is getting a hair cut...
customer #14 comes...
the chair left : 0
customer #15 comes...
customer #15 left...
the baber is working on one...
so the chair left : 1
the baber has done one!
customer #9 is getting a hair cut...
customer #16 comes...
the chair left : 0
customer #17 comes...
customer #17 left...
the baber is working on one...
so the chair left : 1
the baber has done one!
customer #10 is getting a hair cut...
customer #18 comes...
the chair left : 0
customer #19 comes...
customer #19 left...
the baber is working on one...
so the chair left : 1
the baber has done one!
customer #12 is getting a hair cut...
the baber is working on one...
so the chair left : 2
the baber has done one!
customer #14 is getting a hair cut...
the baber is working on one...
so the chair left : 3
the baber has done one!
customer #16 is getting a hair cut...
the baber is working on one...
so the chair left : 4
the baber has done one!
customer #18 is getting a hair cut...
the baber is working on one...
so the chair left : 5
the baber has done one!
从以上结果可以看出,之前的逻辑完美解决了理发师和顾客间存在的Deadlock,Stravation和Critical Section的问题。
完整代码
#include
#include
#include
#include
#define TRUE 1
#define FALSE 0
// 最大椅子数
#define MAX_CHAIR 5
// 最大顾客数
#define MAX_CUSTOMERS 20
// 定义p、v操作
#define p(x) sem_wait(&x)
#define v(x) sem_post(&x)
// 椅子
int chair;
// 理发师和顾客信号量
sem_t baber, customers;
// 互斥信号量 保护临界区(即椅子)
pthread_mutex_t chair_mutex;
// 初始化椅子数和信号量
int init()
{
chair = MAX_CHAIR;
return (sem_init(&baber, 0, 1) || sem_init(&customers, 0, 0));
}
// 理发师线程函数
void* _baber(void *arg)
{
printf("the baber shop opens...\n\n");
while (TRUE)
{
p(customers); // 尝试为一位顾客服务,否则睡觉
// printf("baber wake up...\n");
pthread_mutex_lock(&chair_mutex);
++chair; // 一个顾客去理发,空出一个椅子
printf("the baber is working on one...\nso the chair left : %d\n", chair);
pthread_mutex_unlock(&chair_mutex);
v(baber); // 理发师理完一个
printf("the baber has done one!\n");
sleep(2);
}
}
// 顾客线程函数
void* _customer(void *arg)
{
int * id_p = (int *)arg;
int id = *id_p;
printf("customer #%d comes...\n", id);
pthread_mutex_lock(&chair_mutex); // 想获得椅子
if(chair > 0) { // 还有空椅子
--chair;
v(customers); // 新加入一个顾客
printf("the chair left : %d\n", chair);
pthread_mutex_unlock(&chair_mutex);
p(baber); //等待理发师
printf("customer #%d is getting a hair cut...\n", id);
sleep(1);
}
else
{
pthread_mutex_unlock(&chair_mutex); // 释放锁
printf("customer #%d left...\n", id);
}
}
int main()
{
if(init()) {
printf("initialize semaphore error!\n");
return 0;
}
pthread_t baber_tid;
pthread_t customers_tid[MAX_CUSTOMERS];
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&baber_tid, &attr, _baber, NULL);
for (int i = 0; i < MAX_CUSTOMERS; ++i) {
pthread_create(&customers_tid[i], &attr, _customer, (void *)&i);
sleep(1);
}
pthread_join(baber_tid, NULL);
for (int i = 0; i < MAX_CUSTOMERS; ++i)
pthread_join(customers_tid[i], NULL);
return 0;
}
代码下载
(完)