生产者与消费者问题
实验目的:
了解和熟悉linux系统下的信号量集和共享内存。
实验任务:
使用linux系统提供的信号量集和共享内存实现生产者和消费者问题。
实验要求:
- 写两个程序,一个模拟生产者过程,一个模拟消费者过程;
- 创建一个共享内存模拟生产者-消费者问题中缓冲队列,该缓冲队列有N(例如N=10)个缓冲区,每个缓冲区的大小为1024B,每个生产者和消费者对缓冲区必须互斥访问;
- 由第一个生产者创建信号量集和共享内存,其他生产者和消费者可以使用该信号量集和共享内存;
- 生产者程序:生产者生产产品(即是从键盘输入长度小于1024B的字符)放入空的缓冲区;
- 消费者程序:消费者消费产品(即从满的缓冲区中取出内容在屏幕上打印出来),然后满的缓冲区变为空的缓冲区;
- 多次运行生产者程序和消费者进程,可以产生多个生产者进程和多个消费者进程,这些进程可以共享这些信号量和共享内存,实现生产者和消费者问题;
- 在生产者程序程序中,可以选择:
- 生产产品;
- 退出。退出进程,但信号量和共享内存仍然存在,其他生产者进程和消费者进程还可以继续使用;
- 删除信号量和共享内存。显性删除信号量和共享内存,其他生产者进程和消费者进程都不能使用这些信号量和共享内存。
- 在消费者程序中,可以选择:
- 消费产品;
- 退出。退出进程,但信号量和共享内存仍然存在,其他生产者进程和消费者进程还可以继续使用;
- 删除信号量和共享内存。显性删除信号量和共享内存,其他生产者进程和消费者进程都不能使用这些信号量和共享内存。
实验代码:
生产者代码:
#include<unistd.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/sem.h> #include<sys/shm.h> #define N 4 #define size N*1024+8 union semun{ int val; struct semid_ds *buf; unsigned short *array; }; //信号量集 int main(){ //记录缓冲区开始的位置和结束的位置 int* start; int* end; /*****申请共享内存空间*****/ int shmid; char *viraddr; char buffer[1024]; shmid=shmget(8888,size,0666|IPC_CREAT); viraddr=(char*)shmat(shmid,0,0); start = (int*)viraddr;//缓冲区开始有的位置 end = (int*)(viraddr+4);//缓冲区开始结束的位置 /*****定义PV操作*****/ struct sembuf P,V; P.sem_num=0; P.sem_op=-1; P.sem_flg=SEM_UNDO; V.sem_num=0; V.sem_op=1; V.sem_flg=SEM_UNDO; /*****申请只有一个信号量的信号量集*****/ int emptyid; int fullid; //同步信号量 int mutex; //互斥信号量 union semun arg; if(semget(1000,1,IPC_CREAT|IPC_EXCL)!= -1){
emptyid=semget(1000,1,IPC_CREAT|0666); fullid=semget(2000,1,IPC_CREAT|0666); mutex=semget(3000,1,IPC_CREAT|0666);\ /*****分别对每个信号量赋初值*****/
arg.val=N; semctl(emptyid,0,SETVAL,arg); arg.val=0; semctl(fullid,0,SETVAL,arg); arg.val=1; semctl(mutex,0,SETVAL,arg); *end = *start=0; puts("第一个生产者初始化\n"); }else{ printf("这不是第一个生产者\n"); emptyid=semget(1000,1,0666); fullid=semget(2000,1,0666); mutex=semget(3000,1,0666); }
int flag =0; while(1){ puts("1 生产产品,2 退出,3 撤销信号量集、释放共享内存"); scanf("%d",&flag); getchar(); switch(flag){ case 1: start = (int*)viraddr;//缓冲区开始有的位置 end = (int*)(viraddr+4);//缓冲区开始结束的位置 char* p = viraddr+8+(*end)*1024;//共享内存正文开始位置 semop(emptyid,&P,1); semop(mutex,&P,1); puts("输入生产的信息:"); fgets(buffer,1024,stdin); strcpy(p,buffer); *end = (*end+1)%N; semop(mutex,&V,1); semop(fullid,&V,1); printf("生产成功 start=%d end=%d\n",*start,*end); break; case 2: printf("退出成功\n"); exit(0); break; case 3: shmdt(viraddr); /*****撤销信号量集、释放共享内存*****/ shmctl(shmid,IPC_RMID,0); semctl(emptyid,IPC_RMID,0); semctl(fullid,IPC_RMID,0); semctl(mutex,IPC_RMID,0); printf("撤销成功,退出\n"); exit(0); break; } } return 0; } |
消费者代码:
#include<unistd.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/sem.h> #include<sys/shm.h> #define N 4 #define size N*1024+8 int main(){
//记录缓冲区开始的位置和结束的位置 int* start; int* end; /*****申请只有一个信号量的信号量集*****/ int emptyid; int fullid; //同步信号量 int mutex; //互斥信号量 emptyid=semget(1000,1,0666); fullid=semget(2000,1,0666); mutex=semget(3000,1,0666); /*****定义PV操作*****/ struct sembuf P,V; P.sem_num=0; P.sem_op=-1; P.sem_flg=SEM_UNDO; V.sem_num=0; V.sem_op=1; V.sem_flg=SEM_UNDO; /*****申请共享内存空间*****/ int shmid; char *viraddr; char buffer[1024]; shmid=shmget(8888,size,0666|IPC_CREAT); viraddr=(char*)shmat(shmid,0,0);
int flag =0; while(1){ puts("1 消费产品,2 退出,3 撤销信号量集、释放共享内存"); scanf("%d",&flag); getchar(); switch(flag){ case 1: start = (int*)viraddr;//缓冲区开始有的位置 end = (int*)(viraddr+4);//缓冲区开始结束的位置 char* p = viraddr+8+(*start)*1024; // 消费代码段 semop(fullid,&P,1); semop(mutex,&P,1); printf("消费的消息是: %s",p); *start = (*start+1)%N; printf(" start=%d ",*start); printf(" end=%d \n",*end); semop(mutex,&V,1); semop(emptyid,&V,1); break; case 2: printf("退出成功\n"); exit(0); break; case 3: shmdt(viraddr); /*****撤销信号量集、释放共享内存*****/ shmctl(shmid,IPC_RMID,0); semctl(emptyid,IPC_RMID,0); semctl(fullid,IPC_RMID,0); semctl(mutex,IPC_RMID,0); printf("撤销成功,退出\n"); exit(0); break; } } } |
运行结果:
第一个生产者:
第二个生产者:
消费者:
实验总结:
实验分析:实验的核心是信号量和共享内存的使用,功能选择是比较简单的switch case 语句。
信号量:实验一共使用了三个信号量,其中emptyid和fullid,用于进程同步,mutex用于进程互斥,使用时先P(emptyid/fullid)再P(mutex)可以预防死锁。
判断是不是第一个生产者通过semget(1000,1,IPC_CREAT|IPC_EXCL)!= -1来判断,满足条件则对共享变量和信号量进行创建。
start | end | p |
共享内存:共享内存按这个格式设置:
其中start用于指明缓冲区开始有的位置,end用于指明缓冲区结束的位置,p是共享内存开始的位置;所以共享内存的大小是4*2+1024*N,N个缓冲区。
实验中所有的key(用来指明共享内存或者信号量的)为了不同进程使用的是同一个,全部设置为常数。
遇到的问题:
1. 开始没想到怎么判断是不是第一个生产者,解决办法是通过semget(1000,1,IPC_CREAT|IPC_EXCL)!= -1来设置。
2. 实验中的key如果设置为IPC_PRIVATE,不同的进程间好像是不一样的,解决办法是设置为常数。
生产者代码:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/shm.h>
#define N 4
#define size N*1024+8
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}; //信号量集
int main(){
//记录缓冲区开始的位置和结束的位置
int* start;
int* end;
/*****申请共享内存空间*****/
int shmid;
char *viraddr;
char buffer[1024];
shmid=shmget(8888,size,0666|IPC_CREAT);
viraddr=(char*)shmat(shmid,0,0);
start = (int*)viraddr;//缓冲区开始有的位置
end = (int*)(viraddr+4);//缓冲区开始结束的位置
/*****定义PV操作*****/
struct sembuf P,V;
P.sem_num=0;
P.sem_op=-1;
P.sem_flg=SEM_UNDO;
V.sem_num=0;
V.sem_op=1;
V.sem_flg=SEM_UNDO;
/*****申请只有一个信号量的信号量集*****/
int emptyid;
int fullid; //同步信号量
int mutex; //互斥信号量
union semun arg;
if(semget(1000,1,IPC_CREAT|IPC_EXCL)!= -1){
emptyid=semget(1000,1,IPC_CREAT|0666);
fullid=semget(2000,1,IPC_CREAT|0666);
mutex=semget(3000,1,IPC_CREAT|0666);\
/*****分别对每个信号量赋初值*****/
arg.val=N;
semctl(emptyid,0,SETVAL,arg);
arg.val=0;
semctl(fullid,0,SETVAL,arg);
arg.val=1;
semctl(mutex,0,SETVAL,arg);
*end = *start=0;
puts("第一个生产者初始化\n");
}else{
printf("这不是第一个生产者\n");
emptyid=semget(1000,1,0666);
fullid=semget(2000,1,0666);
mutex=semget(3000,1,0666);
}
int flag =0;
while(1){
puts("1 生产产品,2 退出,3 撤销信号量集、释放共享内存");
scanf("%d",&flag);
getchar();
switch(flag){
case 1:
start = (int*)viraddr;//缓冲区开始有的位置
end = (int*)(viraddr+4);//缓冲区开始结束的位置
char* p = viraddr+8+(*end)*1024;//共享内存正文开始位置
semop(emptyid,&P,1);
semop(mutex,&P,1);
puts("输入生产的信息:");
fgets(buffer,1024,stdin);
strcpy(p,buffer);
*end = (*end+1)%N;
semop(mutex,&V,1);
semop(fullid,&V,1);
printf("生产成功 start=%d end=%d\n",*start,*end);
break;
case 2:
printf("退出成功\n");
exit(0);
break;
case 3:
shmdt(viraddr);
/*****撤销信号量集、释放共享内存*****/
shmctl(shmid,IPC_RMID,0);
semctl(emptyid,IPC_RMID,0);
semctl(fullid,IPC_RMID,0);
semctl(mutex,IPC_RMID,0);
printf("撤销成功,退出\n");
exit(0);
break;
}
}
return 0;
}
消费者代码:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/shm.h>
#define N 4
#define size N*1024+8
int main(){
//记录缓冲区开始的位置和结束的位置
int* start;
int* end;
/*****申请只有一个信号量的信号量集*****/
int emptyid;
int fullid; //同步信号量
int mutex; //互斥信号量
emptyid=semget(1000,1,0666);
fullid=semget(2000,1,0666);
mutex=semget(3000,1,0666);
/*****定义PV操作*****/
struct sembuf P,V;
P.sem_num=0;
P.sem_op=-1;
P.sem_flg=SEM_UNDO;
V.sem_num=0;
V.sem_op=1;
V.sem_flg=SEM_UNDO;
/*****申请共享内存空间*****/
int shmid;
char *viraddr;
char buffer[1024];
shmid=shmget(8888,size,0666|IPC_CREAT);
viraddr=(char*)shmat(shmid,0,0);
int flag =0;
while(1){
puts("1 消费产品,2 退出,3 撤销信号量集、释放共享内存");
scanf("%d",&flag);
getchar();
switch(flag){
case 1:
start = (int*)viraddr;//缓冲区开始有的位置
end = (int*)(viraddr+4);//缓冲区开始结束的位置
char* p = viraddr+8+(*start)*1024;
// 消费代码段
semop(fullid,&P,1);
semop(mutex,&P,1);
printf("消费的消息是: %s",p);
*start = (*start+1)%N;
printf(" start=%d ",*start);
printf(" end=%d \n",*end);
semop(mutex,&V,1);
semop(emptyid,&V,1);
break;
case 2:
printf("退出成功\n");
exit(0);
break;
case 3:
shmdt(viraddr);
/*****撤销信号量集、释放共享内存*****/
shmctl(shmid,IPC_RMID,0);
semctl(emptyid,IPC_RMID,0);
semctl(fullid,IPC_RMID,0);
semctl(mutex,IPC_RMID,0);
printf("撤销成功,退出\n");
exit(0);
break;
}
}
}