目录
实验原理:
实验内容:
实验器材(设备、元器件):
实验步骤:
实验数据及结果分析:
-
实验原理:
考虑n个缓冲区的缓冲池作为一个共享资源,当生产者进程从数据源—文件中读取数据后将会申请一个缓冲区,并将此数据放入缓冲区中。消费者从一个缓冲区中取走数据,并将其中的内容打印输出。当生产者进程正在访问缓冲区时,消费者进程不能同时访问缓冲区,因此缓冲区是个互斥资源。
生产者、消费者程序执行流程如下图所示:
工程实践证明,利用信号量方法实现进程互斥是高效的,一直被广泛采用,通过信号量机制实现生产者、消费者问题,可以通过以下步骤实现。
步骤1:分配具有n个缓冲区的缓冲池,作为共享资源。
步骤2:定义两个资源型信号量empty 和full,empty信号量表示当前空的缓冲区数量,full表示当前满的缓冲区数量。
步骤3:定义互斥信号量mutex,当某个进程访问缓冲区之前先获取此信号量,在对缓冲区的操作完成后再释放此互斥信号量。以此实现多个进程对共享资源的互斥访问。
步骤4:创建3进程(或者线程)作为生产者,4个进程(或者线程)作为消费者。创建一个文件作为数据源,文件中事先写入一些内容作为内容。
步骤5 :编写代码实现生产者进程的工作内容,即从文件中读取数据,然后申请一个empty信号量,和互斥信号量,然后进入临界区操作将读取的数据放入此缓冲区中。并释放empty信号量和互斥信号量。
步骤6:编写代码实现消费者者进程的工作内容,即先申请一个full信号量,和互斥信号量,然后进入临界区操作从缓冲区中读取数据并打印输出。
Linux系统可以采用的信号量程序接口包括:
1、POSIX命名信号量:使用POSIX IPC名字标识,可用于进程或者线程间的同步。
2、POSIX内存信号量:存放在共享内存中,可用于进程或者线程间的同步。
3、SYSTEM V信号量:在内核中维护,可用于进程或者线程间的同步。
- 实验目的:
- 掌握进程、线程的概念,熟悉相关的控制语;
- 掌握进程、线程间的同步原理和方法;
- 掌握进程、线程间的互斥原理和方法。
-
实验内容:
有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池:生产者进程从文件中读取一个数据,并将它存放到一个缓冲区中; 消费者进程从一个缓冲区中取走数据,并输出此数据。生产者和消费者之间必须保持同步原则:不允许消费者进程到一个空缓冲区去取产品;也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
创建3进程(或者线程)作为生产者,4个进程(或者线程)作为消费者。创建一个文件作为数据源,文件中事先写入一些内容作为数据。
生产者和消费者进程(或者线程)都具有相同的优先级。
-
实验器材(设备、元器件):
PC计算机,操作系统:Ubuntu
-
实验步骤:
1. 使用XShell连接终端;
2. 使用vim命令打开文本编辑器进行编码:vim prog2.c\
3. 编写数据文件data.txt
4.编译程序:
gcc prog22.c -o a -lpthread
5. 执行程序:./a
-
实验数据及结果分析:
实验数据 | 类型 | 初始化 |
mutex信号量 | pthread_mutex_t | 1 |
empty_sem | sem_t | M |
full_sem | Sem_t | 0 |
生产者循环次数 | int | 20 |
消费者循环次数 | int | 15 |
通过循环消费者生产者问题,没有发现报错,即没有出现死锁,均正常执行。
实验结果:
成功实现了生产者消费者问题。
代码分析:
消费者函数 consumer():
- void *consumer()
- {
- consumer_id++;
- int id = consumer_id;
- int count = 30;
- while (count)
- {
- count--;
- sleep(1);
- sem_wait(&full_sem); //如果为空则等待
- pthread_mutex_lock(&mutex); //实现互斥
- out %= M;
- printf("消费者%d从%d号缓冲区取出一个数据:%d\n", id, out, buff[out]);
- buff[out] = 0;
- out++;
- pthread_mutex_unlock(&mutex);
- sem_post(&empty_sem);
- }
- }
实现:等待full信号量,加锁,产品取出缓冲区,解锁操作,并在加锁不成功时进行阻塞操作,
通过out%=M实现数组内循环;
生产者函数 producer():
- /*生产者方法*/
- void *producer()
- {
- producer_id++;
- int id = producer_id;
- int count = 40;
- while (count)
- {
- count--;
- sleep(1);
- sem_wait(&empty_sem);
- pthread_mutex_lock(&mutex); //实现互斥
- if (fscanf(fp, "%d", &data) == EOF)
- {
- fseek(fp, 0, SEEK_SET);
- fscanf(fp, "%d", &data);
- }
- in %= M;
- buff[in] = data;
- printf("生产者%d向%d号缓冲区放入了一个数据:%d\n", id, in, buff[in]);
- in++;
- pthread_mutex_unlock(&mutex);
- sem_post(&full_sem);
- }
- }
通过fscanf和fseek实现从文件中读取数据。等待empty信号量,加锁,产品放入缓冲区,解锁操作,并在加锁不成功时进行阻塞操作。
主函数main():
- int main()
- {
- pthread_t id1[producer_Num]; //声明生产者线程的ID数组
- pthread_t id2[consumer_Num]; //声明消费者现车组的ID数组
- int ret1[producer_Num];
- int ret2[consumer_Num];
- int ini1 = sem_init(&empty_sem, 0, M); //缓冲区当前有M个空位
- int ini2 = sem_init(&full_sem, 0, 0); //缓冲区当前有0个可用
- int int3 = pthread_mutex_init(&mutex, NULL);
- fp = fopen("./data.txt", "r"); //打开当前目录下data.txt文件,只读
- if (fp == NULL)
- {
- exit(1); //异常
- }
- //创建生产者线程
- for (int i = 0; i < producer_Num; i++)
- {
- ret1[i] = pthread_create(&id1[i], NULL, producer, NULL);
- }
- //创建消费者线程
- for (int i = 0; i < consumer_Num; i++)
- {
- ret2[i] = pthread_create(&id2[i], NULL, consumer, NULL);
- }
- //销毁线程
- for (int i = 0; i < producer_Num; i++)
- {
- pthread_join(id1[i], NULL);
- }
- //创建消费者线程
- for (int i = 0; i < consumer_Num; i++)
- {
- pthread_join(id2[i], NULL);
- }
- exit(0);
- }
主函数实现了信号量的初始化以及打开文件操作,并创建生产者消费者任务。