问题描述:
抽烟者问题。假设一个系统中有三个抽烟者进程,每个抽烟者不断地卷烟并抽
烟。抽烟者卷起并抽掉一颗烟需要有三种材料:烟草、纸和胶水。一个抽烟者有烟
草,一个有纸,另一个有胶水。系统中还有两个供应者进程,它们无限地供应所有
三种材料,但每次仅轮流提供三种材料中的两种。得到缺失的两种材料的抽烟者在
卷起并抽掉一颗烟后会发信号通知供应者,让它继续提供另外的两种材料。这一过
程重复进行。 请用以上介绍的 IPC 同步机制编程,实现该问题要求的功能。
问题分析:
抽烟者问题的本质就是一个生产者对应多个消费者的问题。首先分析各个对象之间的关系
生产者---消费者(同步关系、互斥访问缓冲区)
消费者---消费者(互斥访问缓冲区)
因此我们需要一个buff初始值为1来表示生产者---消费者、消费者---消费者的互斥关系,需要tobacco、paper、matches来表示生产者与各个消费者之间的同步关系。
使用systemV和共享内存的方式来解决该问题
systemV:System V 信号量是 Unix 系统提供的一种进程间同步机制,用于协调多个进程对共享资源的访问。它们是 System V IPC (Inter-Process Communication) 的一部分,允许进程通过一组信号量来控制对资源的访问。
systemV的常用函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg|(权限位));
//创建一个键值为key,大小为size,标识符为shmflg的共享内存
//key可以自由定义
//size可以自由定义单位是字节
/*shmflg的取值一般是IPC_CREAT|0666标识创建如果不存在指定键值的共享内存就创建一个,同时0666代表允许所有用户读写,权限位通过按位或运算设置。IPC_EXCL与 IPC_CREAT 结合使用,用于确保创建一个新的共享内存段。如果已存在具有相同键值的共享内存段,则返回错误。*/
//返回值是shmid
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
//创建一个键值为ket且拥有nsems个信号量操作标识和权限位为semflg的信号量集
//同时semget也可以通过键值直接获取semid
semid = semget(key, 0, 0666);
//semget用于获取信号量集的ID时。这里的第二个参数设为0,表示不创建新的信号量集,而是获取现有的信号量集。
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
//对键值为semid的信号量集合进行sops操作,nsops是sops的操作数
//semid可以通过键值和semget来获取
//sops是一个指向struct sembuf的指针
struct sembuf {
unsigned short sem_num;
short sem_op;
short sem_flg;
};
sem_num; // 该信号量集合当中的某一个信号量
sem_op; // 操作类型(负数为降低到某一程度,整数为+1,若对应信号量已经等于0了就会被阻塞,直到信号量大于0后被唤醒)
sem_flg; // 操作标志,通常为0
nsops //根据操作决定,执行pv操作时为1
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
//用于控制某一信号量的状态
semid //信号量所在的信号量集
semnum //信号量在信号量集当中的编号
cmd //命令 IPC_RMID:删除信号量集。
//IPC_SET:设置信号量集的属性。
//IPC_STAT:获取信号量集的属性。
//GETVAL:获取指定信号量的值。
//SETVAL:设置指定信号量的值。
//GETALL:获取信号量集中的所有信号量的值。
//SETALL:设置信号量集中的所有信号量的值。
后面的...是cmd所需要的参数
semctl常用于信号量的初始化,删除
代码:
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SHM_KEY 12345
#define SEM_KEY 54321
#define AGENT_SEM_INDEX 0
#define SMOKER1_SEM_INDEX 1
#define SMOKER2_SEM_INDEX 2
#define SMOKER3_SEM_INDEX 3
#define BUFF_SEM_INDEX 4
struct Table {
bool tobacco;
bool paper;
bool matches;
bool buff;
};
int main() {
int shm_id = shmget(SHM_KEY, sizeof(Table), IPC_CREAT | 0666);
if (shm_id == -1) {
std::cerr << "Failed to create shared memory segment." << std::endl;
return 1;
}
Table* table = (Table*)shmat(shm_id, nullptr, 0);
if (table == (Table*)(-1)) {
std::cerr << "Failed to attach to shared memory segment." << std::endl;
return 1;
}
int sem_id = semget(SEM_KEY, 5, IPC_CREAT | 0666);
if (sem_id == -1) {
std::cerr << "Failed to create semaphore set." << std::endl;
return 1;
}
semctl(sem_id, AGENT_SEM_INDEX, SETVAL, 1);
semctl(sem_id, SMOKER1_SEM_INDEX, SETVAL, 0);
semctl(sem_id, SMOKER2_SEM_INDEX, SETVAL, 0);
semctl(sem_id, SMOKER3_SEM_INDEX, SETVAL, 0);
semctl(sem_id, BUFF_SEM_INDEX, SETVAL,1); // buffer
auto agent = [&]() {
sembuf buf;
buf.sem_num = BUFF_SEM_INDEX;
buf.sem_op = -1;
buf.sem_flg = 0;
semop(sem_id,&buf,1);
int cnt=0;
while (true) {
sembuf op;
op.sem_num = AGENT_SEM_INDEX;
op.sem_op = -1;
op.sem_flg = 0;
semop(sem_id, &op, 1);
if(cnt==0){
table->tobacco = false;
table->paper = true;
table->matches = true;
std::cout << "Agent puts matches and paper on the table." << std::endl;
op.sem_num = SMOKER1_SEM_INDEX;
op.sem_op = 1;
semop(sem_id, &op, 1);
}
if(cnt==1){
table->tobacco=true;
table->paper=true;
table->matches=false;
op.sem_num = SMOKER3_SEM_INDEX;
op.sem_op = 1;
semop(sem_id,&op,1);
std::cout<<"Agent puts tobacco and paper on the table"<<std::endl;
}
if(cnt==2){
table->tobacco=true;
table->paper=false;
table->matches=true;
op.sem_num = SMOKER2_SEM_INDEX;
op.sem_op = 1;
semop(sem_id, &op, 1);
std::cout<<"Agent puts tobacco and matches on the table"<<std::endl;
}
cnt++;
cnt%=3;
sleep(3);
buf.sem_num = BUFF_SEM_INDEX;
buf.sem_op = 1;
op.sem_flg = 0;
semop(sem_id,&buf,1);
}
};
auto smoker = [&](int index, bool hasTobacco, bool hasPaper, bool hasMatches) {
while (true) {
sembuf buf;
buf.sem_num = BUFF_SEM_INDEX;
buf.sem_op = -1;
buf.sem_flg = 0;
semop(sem_id,&buf,1);
sembuf op;
op.sem_num = index;
op.sem_op = -1;
op.sem_flg = 0;
semop(sem_id, &op, 1);
if (hasTobacco && !table->tobacco && table->paper && table->matches) {
std::cout << "Smoker with tobacco is smoking." << std::endl;
table->paper = false;
table->matches = false;
op.sem_num = AGENT_SEM_INDEX;
op.sem_op = 1;
semop(sem_id, &op, 1);
} else if (hasPaper && !table->paper && table->tobacco && table->matches) {
std::cout << "Smoker with paper is smoking." << std::endl;
table->tobacco = false;
table->matches = false;
op.sem_num = AGENT_SEM_INDEX;
op.sem_op = 1;
semop(sem_id, &op, 1);
} else if (hasMatches && !table->matches && table->tobacco && table->paper) {
std::cout << "Smoker with matches is smoking." << std::endl;
table->tobacco = false;
table->paper = false;
op.sem_num = AGENT_SEM_INDEX;
op.sem_op = 1;
semop(sem_id, &op, 1);
}
sleep(3);
buf.sem_num = BUFF_SEM_INDEX;
buf.sem_op = 1;
buf.sem_flg = 0;
semop(sem_id,&buf,1);
}
};
std::thread agent_thread(agent);
std::thread smoker1_thread(smoker, SMOKER1_SEM_INDEX, true, false, false);
std::thread smoker2_thread(smoker, SMOKER2_SEM_INDEX, false, true, false);
std::thread smoker3_thread(smoker, SMOKER3_SEM_INDEX, false, false, true);
agent_thread.join();
smoker1_thread.join();
smoker2_thread.join();
smoker3_thread.join();
shmdt(table);
shmctl(shm_id, IPC_RMID, nullptr);
semctl(sem_id, 0, IPC_RMID);
return 0;
}