【操作系统】吸烟者问题
问题描述
吸烟者问题是一个经典的同步问题,涉及三个抽烟者进程和一个供应者进程。每个抽烟者需要三种材料(烟草、纸和胶水)来卷烟,但每个抽烟者只有一种材料。供应者每次提供两种材料,拥有剩下那种材料的抽烟者可以卷烟并抽掉它。
问题分析
-
同步关系:供应者与三个抽烟者之间存在同步关系,供应者提供材料后,抽烟者才能开始卷烟。
-
互斥关系:三个抽烟者之间是互斥的,即同一时间只能有一个抽烟者卷烟。
-
信号量设置:需要设置信号量来控制材料的供应和抽烟者的动作。
解决方案
使用信号量来实现同步和互斥:
-
信号量定义:
-
offer1
、offer2
、offer3
:分别表示供应者提供的不同组合材料(如烟草和纸、烟草和胶水、纸和胶水)。 -
finish
:用于通知供应者当前抽烟者已完成抽烟。
-
伪代码:
-
供应者:
while(1){random = 任意一个整数随机数;random = random % 3;if (random == 0)V(offer1); // 提供烟草和纸else if (random == 1)V(offer2); // 提供烟草和胶水elseV(offer3); // 提供纸和胶水P(finish); // 等待抽烟者完成
}
-
抽烟者:
while(1){P(offerX); // 等待供应者提供材料// 拿走材料,卷烟,抽掉V(finish); // 通知供应者已完成
}
改进
-
由于缓冲区大小为1,四个同步信号量中至多只有一个为1,因此
finish
信号量用于互斥是不必要的
实现详细代码:
1. 供应者代码(supplier.c
)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>#define NUM_SMOKERS 3// 信号量定义
sem_t offer1; // 提供烟草和纸
sem_t offer2; // 提供烟草和胶水
sem_t offer3; // 提供纸和胶水
sem_t finish; // 通知供应者当前抽烟者已完成void* supplier(void* arg) {while (1) {// 随机选择一组材料int random = rand() % NUM_SMOKERS;if (random == 0) {printf("Supplier: Offering tobacco and paper\n");sem_post(&offer1); // 提供烟草和纸} else if (random == 1) {printf("Supplier: Offering tobacco and matches\n");sem_post(&offer2); // 提供烟草和胶水} else {printf("Supplier: Offering paper and matches\n");sem_post(&offer3); // 提供纸和胶水}// 等待抽烟者完成sem_wait(&finish);}
}int main() {pthread_t supplier_thread;// 初始化信号量sem_init(&offer1, 0, 0);sem_init(&offer2, 0, 0);sem_init(&offer3, 0, 0);sem_init(&finish, 0, 0);// 创建供应者线程pthread_create(&supplier_thread, NULL, supplier, NULL);// 主线程可以继续运行其他任务或等待pthread_join(supplier_thread, NULL);// 清理信号量sem_destroy(&offer1);sem_destroy(&offer2);sem_destroy(&offer3);sem_destroy(&finish);return 0;
}
2. 抽烟者代码(smoker.c
)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>#define NUM_SMOKERS 3// 信号量定义
extern sem_t offer1; // 提供烟草和纸
extern sem_t offer2; // 提供烟草和胶水
extern sem_t offer3; // 提供纸和胶水
extern sem_t finish; // 通知供应者当前抽烟者已完成void* smoker1(void* arg) {while (1) {sem_wait(&offer1); // 等待供应者提供烟草和纸printf("Smoker 1: Making a cigarette with tobacco and paper\n");printf("Smoker 1: Smoking...\n");sleep(1); // 模拟抽烟时间sem_post(&finish); // 通知供应者已完成}
}void* smoker2(void* arg) {while (1) {sem_wait(&offer2); // 等待供应者提供烟草和胶水printf("Smoker 2: Making a cigarette with tobacco and matches\n");printf("Smoker 2: Smoking...\n");sleep(1); // 模拟抽烟时间sem_post(&finish); // 通知供应者已完成}
}void* smoker3(void* arg) {while (1) {sem_wait(&offer3); // 等待供应者提供纸和胶水printf("Smoker 3: Making a cigarette with paper and matches\n");printf("Smoker 3: Smoking...\n");sleep(1); // 模拟抽烟时间sem_post(&finish); // 通知供应者已完成}
}int main() {pthread_t smoker1_thread, smoker2_thread, smoker3_thread;// 初始化信号量(假设已经在supplier.c中初始化)extern sem_t offer1, offer2, offer3, finish;// 创建抽烟者线程pthread_create(&smoker1_thread, NULL, smoker1, NULL);pthread_create(&smoker2_thread, NULL, smoker2, NULL);pthread_create(&smoker3_thread, NULL, smoker3, NULL);// 主线程可以继续运行其他任务或等待pthread_join(smoker1_thread, NULL);pthread_join(smoker2_thread, NULL);pthread_join(smoker3_thread, NULL);return 0;
}
代码说明
-
信号量初始化:
-
在
supplier.c
中初始化信号量offer1
、offer2
、offer3
和finish
。 -
在
smoker.c
中声明这些信号量为外部变量。
-
-
供应者逻辑:
-
供应者随机选择一组材料并提供。
-
使用
sem_post
通知一个抽烟者可以开始卷烟。 -
使用
sem_wait
等待抽烟者完成。
-
-
抽烟者逻辑:
-
每个抽烟者等待特定的材料组合。
-
使用
sem_wait
等待供应者提供材料。 -
模拟卷烟和抽烟过程。
-
使用
sem_post
通知供应者已完成。
-
编译与运行
-
将上述代码保存为
supplier.c
和smoker.c
。 -
使用以下命令编译代码:
gcc -pthread -o supplier supplier.c gcc -pthread -o smoker smoker.c
-
运行程序:
./supplier & ./smoker
输出示例
运行程序后,你将看到类似以下的输出:
Supplier: Offering tobacco and paper
Smoker 1: Making a cigarette with tobacco and paper
Smoker 1: Smoking...
Supplier: Offering tobacco and matches
Smoker 2: Making a cigarette with tobacco and matches
Smoker 2: Smoking...
Supplier: Offering paper and matches
Smoker 3: Making a cigarette with paper and matches
Smoker 3: Smoking...
总结
这个实现通过信号量控制供应者和抽烟者之间的同步,确保每次只有一个抽烟者能够获取材料并开始卷烟。