问题描述

实验三、生产者消费者问题(20分)
1、在Windows和Linux系统下通过进程编程模拟生产者消费者算法
2、设计一个大小为4的缓冲区,初始为"-”,表示空
3、创建3个生产者,每个生产者
-随机等待一段3秒以内(含3秒)的时间,向缓冲区随机添加一个姓名首字母(大写),如王全玉的首字母为W、Q、Y,即每次从这三个字母中取一个;
-若缓冲区已满,等待消费者取走字母后再添加;
-重复4次;
4、创建4个消费者,每个消费者
-随机等待一段时间,从缓冲区读取字母
-若缓冲区为空,等待生产者添加字母后再读取
-重复3次
5、需打印每次操作内容
-生产者打印:生产者本次写入缓冲区字母
-消费者打印:消费者本次取走的字母
-需打印缓冲区内容
-按先生产的商品先消费原则

相关知识

共享文件

  1. 什么是共享内存?
    共享内存就是允许两个或多个进程共享一定的存储区。就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。因为数据不需要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,所以这是最快的一种IPC。
    注:共享内存没有任何的同步与互斥机制,所以要使用信号量来实现对共享内存的存取的同步。

  2. 共享内存特点和优势
    当中共享内存的大致原理相信我们可以看明白了,就是让两个进程地址通过页表映射到同一片物理地址以便于通信,你可以给一个区域里面写入数据,理所当然你就可以从中拿取数据,这也就构成了进程间的双向通信,而且共享内存是IPC通信当中传输速度最快的通信方式没有之一,理由很简单,客户进程和服务进程传递的数据直接从内存里存取、放入,数据不需要在两进程间复制,没有什么操作比这简单了。再者用共享内存进行数据通信,它对数据也没啥限制。
    最后就是共享内存的生命周期随内核。即所有访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在(除非显式删除共享内存区域对象),在内核重新引导之前,对该共享内存区域对象的任何改写操作都将一直保留;简单地说,共享内存区域对象的生命周期跟系统内核的生命周期是一致的,而且共享内存区域对象的作用域范围就是在整个系统内核的生命周期之内。

  3. 缺陷
    但是,共享内存也并不完美,共享内存并未提供同步机制,也就是说,在一个服务进程结束对共享内存的写操作之前,并没有自动机制可以阻止另一个进程(客户进程)开始对它进行读取。这明显还达不到我们想要的,我们不单是在两进程间交互数据,还想实现多个进程对共享内存的同步访问,这也正是使用共享内存的窍门所在。基于此,我们通常会用平时常谈到和用到 信号量来实现对共享内存同步访问控制。

  4. 与共享内存有关的函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
1.创建共享内存
int shmget(key_t key, size_t size, int shmflg);        //成功返回共享内存的ID,出错返回-1
2.操作共享内存———>shmctl()函数
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);   //成功返回0,出错返回-1
3.挂接操作———>shmat()函数
创建共享存储段之后,将进程连接到它的地址空间
void *shmat(int shm_id, const void *shm_addr, int shmflg); //成功返回指向共享存储段的指针,出错返回-1
4.分离操作———>shmdt()函数
该操作不从系统中删除标识符和其数据结构,要显示调用shmctl(带命令IPC_RMID)才能删除它
12int shmdt(const void *shmaddr);             //成功返回0,出错返回-1

信号量

semget函数
//它的作用是创建一个新信号量或取得一个已有信号量
int semget(key_t key, int num_sems, int sem_flags);
semctl函数
int semctl(int sem_id, int sem_num, int command, ...);   
前两个参数与前面一个函数中的一样,command通常是下面两个值中的其中一个 
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号
量第一次使用前对它进行设置。 
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
semop函数
它的作用是改变信号量的值,原型为:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);  
sem_id是由semget返回的信号量标识符,sembuf结构的定义如下:
struct sembuf{  
   short sem_num;//除非使用一组信号量,否则它为0  
   short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                  //一个是+1,即V(发送信号)操作。  
   short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,  
                //并在进程没有释放该信号量而终止时,操作系统释放信号量  
};

代码


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include<iostream>
#include<cstring>
using namespace std; 
#define NEED_P 3            //生产者数量
#define NEED_C 4            //消费者数量
#define WORKS_P 4           //生产次数
#define WORKS_C 3           //消费次数
#define BUF_LENGTH (sizeof(struct my_buffer))
#define LETTER_NUM 4
#define SHM_MODE 0600
#define SEM_ALL_KEY 1234
#define SEM_EMPTY  0
#define SEM_FULL   1            
#define MUTEX      2
//缓冲区结构(循环队列)
struct my_buffer{     
   char letter[LETTER_NUM];     //存放字符数组    
   int in;                      //写指针    
   int out;                     //读指针
};
struct my_buffer * shmptr;              //指向所创建的结构体的指针
int shm_id, sem_id;                     //信号量标识符
//得到3以内的一个随机数
int get_random(){    
    int t;    
    srand((unsigned)(getpid() + time(NULL)));    
    t = rand() % 3;    
    return t;
}
 
//得到A~Z的一个随机字母
char get_letter()
{    
    char a;    
    srand((unsigned)(getpid() + time(NULL)));    
    a = (char)((char)(rand() % 26) + 'A');    
    return a;
}
 
//P操作
void p(int sem_id, int sem_num)
{    
    struct sembuf xx;    
    xx.sem_num = sem_num;                    //操作信号在信号集中的编号,第一个信号的编号是0         
    xx.sem_op = -1;                          //获取资源的使用权    xx.sem_flg = 0;    
    semop(sem_id, &xx, 1);}
 
//V操作
void v(int sem_id, int sem_num)
{    
    struct sembuf xx;    
    xx.sem_num = sem_num;                       //操作信号在信号集中的编号,第一个信号的编号是0    
    xx.sem_op = 1;                              //获取资源的使用权    
    xx.sem_flg = 0;    
    semop(sem_id, &xx, 1);
}
void Creat_Share_MM(){    //创建共享文件区    
    shm_id = shmget(IPC_PRIVATE, BUF_LENGTH, SHM_MODE);    
    if ( shm_id < 0)    {        printf("Error on shmget.\n");              exit(1);    }    //把共享内存区对象映射到调用进程的地址空间    shmptr = (my_buffer *)shmat(shm_id, 0, 0);    //初始化共享文件区    shmptr->in = 0;    shmptr->out = 0;    shmptr->is_empty = 1;    memset(shmptr->letter,'-',4*sizeof(char));    //printf("Creat share memory.\n");
}void Creat_Semaphore(){    //创建3个新信号量    sem_id = semget(SEM_ALL_KEY,            //ftok生成的键值                    3,                      //新集合中创建信号量的数目                    IPC_CREAT | 0660        //打开信号量的方式,若存在啊,则操作失败                    ); 
    if (sem_id >= 0)    {        printf("Semaphore created.\n");    }    semctl(sem_id, SEM_EMPTY, SETVAL, LETTER_NUM);    semctl(sem_id, SEM_FULL, SETVAL, 0);    semctl(sem_id,MUTEX,SETVAL,1); }
//主函数
int main(int argc, char * argv[])
{   
    Creat_Share_MM();
    Creat_Semaphore();
    int num_p = 0, num_c = 0;
    pid_t pid_p, pid_c;
    int i, j;        
    char ch;                          //随机生成的字母
    while ((num_p++) < NEED_P)    
    {       
        if ((pid_p = fork()) < 0)       
         {            
             printf("Error on fork.\n");            
             exit(1);        
         }        
        //如果是子进程,开始创建生产者        
        if (pid_p == 0)        
        {            //将共享内存区对象映射到调用进程的地址空间            
            shmptr = (my_buffer *)shmat(shm_id, 0, 0);            
            for (i = 0; i < WORKS_P; i++)            
            {                                
                p(sem_id, SEM_EMPTY);                                       //对资源进行p操作                
                p(sem_id,MUTEX);                
                shmptr->letter[shmptr->out] = ch = get_letter();           //随机生成一个字母放入缓冲区                
                shmptr->out = (shmptr->out + 1) % LETTER_NUM;             //尾指针后移                
                //shmptr->is_empty = 0;                                     //空标志置0                
                //输出缓冲区字符                
                printf("--------------------------------\n");                
                printf("Producer %d Produce '%c'.  %d\n", num_p, ch, i );                
                printf("缓存区字符:%c %c %c %c\n",shmptr->letter[0],shmptr->letter[1],shmptr->letter[2],shmptr->letter[3]);                
                fflush(stdout);                
                v(sem_id,MUTEX);                
                v(sem_id, SEM_FULL);                                        //对资源进行v操作                           
                sleep(get_random());                                        //等待随机时间            }            
                shmdt(shmptr);//断开共享内存连接            
                exit(0);        
            }
    }
 
    while (num_c++ < NEED_C)
    {
        if ((pid_c = fork()) < 0)
        {
            printf("Error on fork.\n");
            exit(1);
        }        
        //如果是子进程,开始创建消费者       
         if (pid_c == 0)        
         {           
             shmptr = (my_buffer *)shmat(shm_id, 0, 0);            
             for (i = 0; i < WORKS_C; i++)            
             {                
                 p(sem_id, SEM_FULL);//对资源进行p操作                
                 p(sem_id,MUTEX);                
                 ch = shmptr->letter[shmptr->in];                     //取出缓冲区第一个字符                
                 shmptr->letter[shmptr->in]='-';                      //                
                 shmptr->in = (shmptr->in + 1) % LETTER_NUM;        //头指针后移                
                 //shmptr->is_empty = (shmptr->in == shmptr->out);                //输出缓冲区字符                             
                  printf("--------------------------------\n");                
                  printf("Consumer %d gets '%c'.  %d\n", num_c, ch, i);                
                  printf("缓存区字符:%c %c %c %c\n",shmptr->letter[0],shmptr->letter[1],shmptr->letter[2],shmptr->letter[3]);                //
                  fflush(stdout);                    //清除输出缓冲区,并输出缓存区内容                
                  v(sem_id,MUTEX);                
                  v(sem_id, SEM_EMPTY);              //对资源进行v操作                
                  sleep(get_random());                  //等待随机时间            
              }            
              shmdt(shmptr);                                //断开共享内存连接            
              exit(0);       
          }
    }
    
    //主控程序最后退出   
    while(wait(0) != -1);        //等待所有子进程结束    
    shmdt(shmptr);    
    shmctl(shm_id, IPC_RMID, 0);    
    semctl(sem_id, IPC_RMID, 0);           
    fflush(stdout);          //清除输出缓冲区,并输出缓存区内容    
    printf("--------------end---------------\n");    
    exit(0);
}
Logo

更多推荐