实验目的
生产者/消费者问题的实现:
- 掌握进程、线程的概念,熟悉相关的控制语;
- 掌握进程、线程间的同步原理和方法;
- 掌握进程、线程间的互斥原理和方法;
- 掌握使用信号量原语解决进程、线程间互斥和同步方法。
实验原理
该问题是描述有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有 n 个缓冲区的缓冲池:生产者进程从文件中读取一个数据,并将它存放到一个缓冲区中;消费者进程从一个缓冲区中取走数据,并输出此数据。生产者和消费者之间必须保持同步原则:不允许消费者进程到一个空缓冲区去取产品;也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
- 关系分析:生产者和消费者对缓存区访问时互斥关系;生产者生产内容后,消费者才能消费是同步关系。
- 整理思路:两类进程(或者线程)同时存在互斥和同步关系。
- 信号量设置:
①互斥信号量 mutex,控制缓存区的互斥访问,初始值为 1。
②同步信号量 full,记录缓存区中已使用的数,初始值为 0。
③同步信号量 empty,记录缓存区未使用的数,初始值为 N。 - PV 操作在同步和互斥的应用:
①同步问题:如果某行为要使用某种资源,就在行为之前 P 操作该资源;如果某行为会提供某种资源,就在行为之后 V 操作该资源。
②互斥问题:P、V 操作要和使用互斥资源的行为紧密结合使用。 - 同步与互斥的区别:
①同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
②互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
实验内容
在 Linux 系统下实现教材 2.5.1 节中所描述的生产者/消费者问题。
(1)创建 3 个进程(或线程)作为生产者,4 个进程(或线程)作为消费者。创建一个文件作为数据源,文件中事先写入一些内容作为数据;
(2)生产者和消费者进程(或者线程)都具有相同的优先级;
(3)采用信号量方式解决。
实验器材
硬件环境:个人计算机
操作系统:CentOS 7
实验步骤
(1)生产者/消费者问题的实现流程图
(2)生产者/消费者问题的实现代码
-
分配具有 N 个缓冲区的缓冲池。
定义两个资源型信号量 empty 和 full,empty 信号量表示当前空的缓冲区数量,full 表示当前满的缓冲区数量。定义互斥信号量 mutex,当某个进程访问缓冲区之前先获取此信号量,在对缓冲区的操作完成后再释放此互斥信号量。以此实现多个进程对共享资源的互斥访问。
-
创建 3 个线程作为生产者,4 个线程作为消费者。
-
编写代码实现生产者进程的工作内容。
先随机生成一个字母作为生产产品,然后取一个空缓冲区(empty-1),缓冲区加锁,投放产品生产并移动下标 in,然后缓冲区解锁,增加一个满缓冲区(full+1)。
-
编写代码实现消费者者进程的工作内容。
先取一个满缓冲区(full-1),缓冲区加锁,消费产品并移动下标 out,然后缓冲区解锁,增加一个空缓冲区(empty-1)。
实验结果
//The Producer-consumer Problem
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
#define N 5
#define PRODUCER_NUM 3
#define CONSUMER_NUM 4
typedef char item;
item buffer[N]; //公用缓冲池,具有N个缓冲区
char nextp, nextc;
int in = 0, out = 0; //产品进出缓冲区时的缓冲区下标
sem_t mutex, full, empty;
void* Producer(void* arg) {
int proID = *(int*)arg; //生产者编号
while (1) {
nextp = rand() % 26 + 'A'; //producer an item nextp
sem_wait(&empty);
sem_wait(&mutex); //加锁
buffer[in] = nextp; //产生一个产品
usleep(5); //生产中
printf("Producer %d produce %c in position %d\n", proID, nextp, in);
in = (in + 1) % N; //移动下标
sem_post(&mutex); //解锁
sem_post(&full);
}
}
void* Consumer(void* arg) {
int conID = *(int*)arg; //消费者编号
while (1) {
sem_wait(&full);
sem_wait(&mutex); //加锁
nextc = buffer[out]; //消费一个产品
printf("Consumer %d tack product %c in position %d\n", conID, nextc, out);
out = (out + 1) % N; //移动下标
usleep(5); //消费中
sem_post(&mutex); //解锁
sem_post(&empty);
//consumer an item nextc
}
}
int main() {
srand(time(0));
sem_init(&mutex, 0, 1); //互斥信号量mutex,控制缓存区的互斥访问
sem_init(&full, 0, 0); //同步信号量full,记录缓存区中已使用的数
sem_init(&empty, 0, N); //同步信号量empty,记录缓存区未使用的数
pthread_t p[PRODUCER_NUM], c[CONSUMER_NUM]; //生产者线程和消费者线程
int i;
for (i = 0; i < PRODUCER_NUM; i++) //创建生产者线程放入线程池
pthread_create(&p[i], NULL, Producer, &i);
for (i = 0; i < CONSUMER_NUM; i++) //创建消费者线程放入线程池
pthread_create(&c[i], NULL, Consumer, &i);
for (i = 0; i < PRODUCER_NUM; i++) //等待生产者线程运行结束
pthread_join(p[i], NULL);
for (i = 0; i < CONSUMER_NUM; i++) //等待消费者线程运行结束
pthread_join(c[i], NULL);
return 0;
}
操作系统基础实验: