一、实验名称:生产者/消费者问题的实现
二、实验目的:
(1)掌握进程、线程的概念,熟悉相关的控制语。
(2)掌握进程、线程间的同步原理和方法。
(3)掌握进程、线程间的互斥原理和方法。
(4)掌握使用信号量原语解决进程、线程间互斥和同步方法。
(5)利用信号量、互斥量实现生产者/消费者问题。
三、实验原理:
(1)n个缓冲区的缓冲池作为一个临界资源:当生产者任务从数据源—文件中读取数据后将会申请一个缓冲区,并将此数据放入缓冲区中。消费者任务从一个缓冲区中取走数据,并将其中的内容打印输出。
(2)当一个生产者任务正在访问缓冲区时,其他生产者和消费者任务不能访问缓冲区;当一个消费者任务正在访问缓冲区时,其他其他生产者和消费者任务不能访问缓冲区;使用互斥量实现对缓冲池的互斥访问。
(3)生产者任务在向缓冲池放入数据之前需要判断缓冲池中是否还有空的缓冲区,如果有则向空的缓冲区写入,如果没有则等待;消费者任务在从缓冲池读取数据之前需要判断缓冲池中是否有已经写入数据的缓冲区,如果有则读取已经写入数据的缓冲区,如果没有则等待;使用两个信号量:一个代表空缓冲区的数量,一个代表已经写入数据的缓冲区数量。
四、实验内容:
(1)有一群生产者任务在生产产品,并将这些产品提供给消费者任务去消费。为使生产者任务与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池:生产者任务从文件中读取一个数据,并将它存放到一个缓冲区中;消费者任务从一个缓冲区中取走数据,并输出此数据(printf);生产者和消费者之间必须保持同步原则:不允许消费者任务到一个空缓冲区去取产品,也不允许生产者任务向一个已装满产品且尚未被取走的缓冲区中投放产品。
(2)创建3个进程(或者线程)作为生产者任务,4个进程(或者线程)作为消费者任务
(3)创建一个文件作为数据源,文件中事先写入一些内容作为数据(字符串或者数值)
(4)生产者和消费者任务(进程或者线程)都具有相同的优先级
五、实验器材(设备、元器件):
- 学生每人一台PC,安装Windows8操作系统。
- 个人PC安装VMware虚拟机和Ubuntu系统。
六、实验步骤:
(1)在Ubuntu系统中使用“Ctrl+Alt+T”打开终端;
(2)使用vim编写数据文件data.txt
(3)使用“vim lab2.c”命令打开vim文本编辑器 ,并编写lab2.c源程序实现生产者/消费者问题
(4)编译程序:gcc lab2.c -o test
(5)执行程序:./test
七、代码实现
为解决生产者/消费者问题中的同步与互斥问题,使用互斥量mutex来实现对缓冲区的互斥访问,使用信号量full、empty分别代表满缓冲区的数量以及空缓冲区的数量来实现生产者与消费者的同步问题,代码如下所示:
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#include<unistd.h>
#define N 5 //定义缓冲区长度
pthread_mutex_t mutex; // 声明互斥量
sem_t full,empty; // 声明两个信号量
void init();
void destroy();
void *consumer(void *);
void *producer(void *);
char buffer[N];
int in = 0,out = 0;
FILE* fp;
int main(){
init(); // 初始化互斥量跟信号量
pthread_t p[3],c[4];
char* id[] = {"1","2","3","4"};
fp = fopen("/home/qinxinpeng/data.txt","r");
if(fp == NULL){
printf("打开文件失败!");
return 0;
}
int i;
for(i = 0; i < 3; i++){
pthread_create(&p[i],NULL,producer,(void *)id[i]);
}
for(i = 0; i < 4; i++){
pthread_create(&c[i],NULL,consumer,(void *)id[i]);
}
for(i = 0; i < 3; i++){
pthread_join(p[i],NULL);
}
for(i = 0; i < 4; i++){
pthread_join(c[i],NULL);
}
destroy();
fclose(fp);
return 0;
}
// 消费者函数
void *consumer(void *arg){
int id = atoi((char*)arg);
int count = 6;
while(count > 0){
char nextch;
sem_wait(&full);
pthread_mutex_lock(&mutex);
nextch = buffer[out];
out = (out + 1) % N;
printf("消费者%d从缓冲区取走数据%c\n",id,nextch);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
sleep(1);
count--;
}
}
// 生产者函数
void *producer(void *arg){
int id = atoi((char*)arg);
int count = 8;
while(count > 0){
char nextch = fgetc(fp);// 生产字符
sem_wait(&empty);
pthread_mutex_lock(&mutex);
buffer[in] = nextch;
in = (in + 1) % N;
printf("生产者%d向缓冲区添加数据%c\n",id,nextch);
pthread_mutex_unlock(&mutex);
sem_post(&full);
sleep(1);
count--;
}
}
void init(){
mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
sem_init(&full,0,0);
sem_init(&empty,0,N);
int i;
for(i = 0; i < N; i++){
buffer[i] = '\0';
}
}
void destroy(){
sem_destroy(&full);
sem_destroy(&empty);
}
八、总结及心得体会
通过本次实验,进一步掌握了进程、线程的概念以及两者之间的同步原理和方法、互斥原理和方法,熟悉了相关的原语。同时也掌握了使用信号量原语解决进程、线程间互斥和同步方法,并能够利用信号量、互斥量实现生产者/消费者问题。