吸烟者问题
问题描述
假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)
角色与行为
供应者
- 无限循环地提供香烟和火柴的组合
- 等待吸烟者完成吸烟的通知
- 同步机制确保每次只为一个吸烟者提供材料
吸烟者
- 请求香烟和火柴
- 吸烟
- 吸烟完成后通知供应者
同步与互斥
信号量分析
-
使用信号量实现同步
- 组合一:提供纸和胶水 offer1
- 组合二:提供烟草和胶水 offer2
- 组合三:提供烟草和纸 offer3
-
互斥访问供应区
- 桌子信号量 desk
- 互斥 mutex
semaphore offer1 = 0;
semaphore offer2 = 0;
semaphore offer3 = 0;
semaphore desk = 1;
semaphoer mutex = 1;
伪代码
// 定义信号量
semaphore offer1 = 0; // 用于表示物品1是否被提供
semaphore offer2 = 0; // 用于表示物品2是否被提供
semaphore offer3 = 0; // 用于表示物品3是否被提供
semaphore desk = 1; // 用于表示桌子是否可用
semaphore mutex = 1; // 用于保证进程 p0 的互斥访问
// 进程 p0:提供物品
process p0() {
i++; // 增加计数器 i
i = i % 3; // i 的值在 0, 1, 2 循环
p(desk); // 申请桌子资源
p(mutex); // 进入临界区,保证互斥访问
if (i == 1) // 根据 i 的值提供不同的物品
v(offer1); // 提供组合1
else if (i == 2)
v(offer2); // 提供组合2
else
v(offer3); // 提供组合3
v(mutex); // 退出临界区
}
// 进程 p1:烟民1
process p1() {
p(offer1); // 等待物品1
p(mutex); // 进入临界区,保证互斥访问
吸烟;
v(mutex); // 退出临界区
v(desk); // 释放桌子
}
// 进程 p2:烟民2
process p2() {
p(offer2); // 等待物品2
p(mutex); // 进入临界区,保证互斥访问
吸烟;
v(mutex); // 退出临界区
v(desk); // 释放桌子
}
// 进程 p3:烟民3
process p3() {
p(offer3); // 等待物品3
p(mutex); // 进入临界区,保证互斥访问
吸烟;
v(mutex); // 退出临界区
v(desk); // 释放桌子
}
思考:是否可以继续优化信号量?
经过分析发现mutex可以去掉,因为desk已经完成了互斥访问。优化后伪代码如下:
semaphore offer1 = 0;
semaphore offer2 = 0;
semaphore offer3 = 0;
semaphore desk = 1;
process p0() {
i++;
i = i % 3;
p(desk);
if (i == 1)
v(offer1);
else if (i == 2)
v(offer2);
else
v(offer3);
}
process p1() {
p(offer1);
吸烟;
v(desk);
}
process p2() {
p(offer1);
吸烟;
v(desk);
}
process p3() {
p(offer1);
吸烟;
v(desk);
}
c语言程序验证结果:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
// 初始化信号量
sem_t offer1; // 表示桌上有一种组合材料
sem_t offer2; // 表示桌上有另一种组合材料
sem_t offer3; // 表示桌上有第三种组合材料
sem_t desk; // 表示桌子是否空闲
int i = 0;
// 代理人线程函数
void* agent(void* arg) {
while (1) {
i++;
i = i % 3;
sem_wait(&desk); //P(desk);
if (i == 0) {
printf("\n");
printf("代理人提供烟草和纸\n");
sem_post(&offer1); //V(offer1);
} else if (i == 1) {
printf("\n");
printf("代理人提供纸和火柴\n");
sem_post(&offer2); //V(offer2);
} else {
printf("\n");
printf("代理人提供火柴和烟草\n");
sem_post(&offer3); //V(offer3);
}
sleep(1); // 模拟代理人放置材料的时间
}
return NULL;
}
// 吸烟者线程函数(拥有烟草的吸烟者)
void* smoker_with_tobacco(void* arg) {
while (1) {
sem_wait(&offer2); // 等待纸和火柴
printf("拥有烟草的吸烟者正在吸烟\n");
sleep(0.5); // 模拟吸烟时间
sem_post(&desk);
}
return NULL;
}
// 吸烟者线程函数(拥有纸的吸烟者)
void* smoker_with_paper(void* arg) {
while (1) {
sem_wait(&offer3); // 等待火柴和烟草
printf("拥有纸的吸烟者正在吸烟\n");
sleep(0.5); // 模拟吸烟时间
sem_post(&desk);
}
return NULL;
}
// 吸烟者线程函数(拥有火柴的吸烟者)
void* smoker_with_match(void* arg) {
while (1) {
sem_wait(&offer1); // 等待烟草和纸
printf("拥有火柴的吸烟者正在吸烟\n");
sleep(0.5); // 模拟吸烟时间
sem_post(&desk);
}
return NULL;
}
int main() {
// 初始化信号量
sem_init(&offer1, 0, 0);
sem_init(&offer2, 0, 0);
sem_init(&offer3, 0, 0);
sem_init(&desk, 0, 1);
// 创建线程
pthread_t agent_thread;
pthread_t smoker_tobacco_thread;
pthread_t smoker_paper_thread;
pthread_t smoker_match_thread;
pthread_create(&agent_thread, NULL, agent, NULL);
pthread_create(&smoker_tobacco_thread, NULL, smoker_with_tobacco, NULL);
pthread_create(&smoker_paper_thread, NULL, smoker_with_paper, NULL);
pthread_create(&smoker_match_thread, NULL, smoker_with_match, NULL);
// 等待线程结束(实际中这些线程是无限循环的,不会自然结束)
pthread_join(agent_thread, NULL);
pthread_join(smoker_tobacco_thread, NULL);
pthread_join(smoker_paper_thread, NULL);
pthread_join(smoker_match_thread, NULL);
// 销毁信号量
sem_destroy(&offer1);
sem_destroy(&offer2);
sem_destroy(&offer3);
sem_destroy(&desk);
return 0;
}
##运行结果分析:正确