#include #include #include #include #include //宏定义一些变量和信号量,方便进行调试#define N 10 //缓冲区的大小#define ProNum 2 //生产者的数量#define ConNum 2 //消费者的数量#define SleepTime 3 //生产者消费者操作完后的暂停时间typedef int semaphore; //定义信号量的类型均为整数typedef char element; //定义生产者生产的元素都是字符element buffer[N] = {0}; //定义长度为 N 的缓冲区,内容均是字符型int in = 0; //缓冲区下一个存放产品的索引int out = 0; //缓冲区下一个可以使用的产品索引int ProCount = 0; //生产者序号统计int ConCount = 0; //消费者序号统计double StartTime; //程序开始时间记录,方便输出执行语句的时间//定义并初始化信号量//mutex 用来控制生产和消费每个时刻只有一个在进行,初始化为 1,用来实现互斥//empty 表示剩余的可用来存放产品的缓冲区大小,用来实现同步,刚开始有 N 个空位置//full 表示当前缓冲区中可用的产品数量,用来实现同步,刚开始没有产品可以使用semaphore mutex = 1, empty = N, full = 0;//生产者线程void *Producer(void *args){int *x = (int *)args;element eletemp;int sum = 0;while (sum < 10){while (empty <= 0) //P(empty),判断是否有空位置供存放生产的产品,同步,没有空位则等待并输出提示语句printf("%.4lfs| 缓 冲 区 已 满 ! 生 产 者 :%d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);empty--; //等到一个空位置,先把他占用while (mutex <= 0) //P(mutex),判断当前临界区中是否有进程正在生产或者消费,如果有则等待并输出提示语句printf("%.4lfs|缓冲区有进程正在操作 !生产者 :%d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);mutex--;//等到占用进程出临界区,进入临界区并占用eletemp = (rand() % 26) + 65; //生产一个产品,即任意产生一个 A~Z 的字母//输出生产成功的字样printf("%.4lfs| 生产者: %d 生产一个产品: %c ,并将其放入缓冲区: %d 位置\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x, eletemp, in);buffer[in] = eletemp; //将生产的产品存入缓冲区sum++;in = (in + 1) % N; //缓冲区索引更新mutex++; //V(mutex),临界区使用完成释放信号full++; //V(full),实现同步,释放 fullsleep(SleepTime);//当前生产者生产完成,休眠一定时间才能继续}}//消费者线程void *Consumer(void *args){int *x = (int *)args;int sum = 0;while (sum < 10){while (full <= 0) //P(full),判断缓冲区当中是否有产品可以消费,同步如果没有产品则等待同时输出提示语句printf("%.4lfs| \t\t\t\t\t\t\t 缓冲区为空!消费者: %d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);full--; //等到有一个产品,消费这个产品while (mutex <= 0) //P(mutex),判断临界区是否有进程在处理,如果有则等待并输出提示语句printf("%.4lfs| \t\t\t\t\t\t\t 缓冲区有进程正在操作! 消费者 : %d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);mutex--; //等到临界区为空,则进入缓冲区消费//输出消费成功的语句printf("%.4lfs| \t\t\t\t\t\t\t 消费者: %d 消费一个产品: %c , 产品位于位于缓冲区 : %d 位置 \n", (clock() - StartTime) / CLOCKS_PER_SEC, *x, buffer[out], out);buffer[out] = 0; //更新缓冲区,把消费掉的产品清空sum++;out = (out + 1) % N; //更新缓冲区索引mutex++; //消费完成退出缓冲区,释放资源empty++; //消费完成产生一个空位,进行同步sleep(SleepTime); //当前消费者消费了一个产品,休眠一段时间再继续}}int main(){//记录程序开始的时间,方便记录消费者和生产者活动的时间StartTime = clock();//产生随机种子,生产的时候随机生产一个字符srand((int)time(NULL));printf(" 时间");printf("\t\t 生产者动态显示");printf("\t\t\t\t 消费者动态显示\n");printf("======================================================================================================================\n");//定义一个线程数组,存储所有的消费者和生产者线程pthread_t threadPool[ProNum + ConNum];int t1=1;int t2=2;pthread_create(&threadPool[0], NULL, Producer, (void *)&t1);pthread_create(&threadPool[1], NULL, Producer, (void *)&t2);pthread_create(&threadPool[2], NULL, Consumer, (void *)&t1);pthread_create(&threadPool[3], NULL, Consumer, (void *)&t2);void *result;for (int i = 0; i < ProNum + ConNum; i++) //等待线程结束并回收资源,线程之间同步{if (pthread_join(threadPool[i], &result) == -1){printf("fail to recollect\n"); //进程回收,如果回收失败输出错误提示exit(1); //结束线程}}}

操作系统生产者消费者实验用C语言来实现。

1、数据结构
(1)用变量N表示缓冲区的个数;用ProNum 2表示生产者的数量;用ConNum 2表示消费者的数量;定义一个长度为N的缓冲区:element buffer[N] = {0}。
(2)double StartTime:程序开始时间记录,方便输出执行语句的时间
(3)定义并初始化信号量:mutex 用来控制生产和消费每个时刻只有一个在进行,初始化为 1,用来实现互斥。
(4)结构体变量empty:表示剩余的可用来存放产品的缓冲区大小,用来实现同步,刚开始有 N 个空位置。
(5)结构体变量full:表示当前缓冲区中可用的产品数量,用来实现同步,刚开始没有产品可以使用。
(6)用整型变量ProCount表示生产者序号。
(7)用整型变量ConCount表示消费者序号。
(8)用整型变量in表示缓冲区下一个存放产品的索引。
(9)用整型变量out表示缓冲区下一个可以使用的产品索引。

  1. 程序结构
    (1)程序描述
    (1) 进入主函数
    (2) 初始化缓冲区、消费请求队列及部分同步对象
    (3) 提取线程信息
    (4) 完成线程相关同步对象的初始化
    (5) 创建线程,模拟生产者和消费者
    (6) 等待所有线程结束
    (7) 程序结束
    (8) 消费者
    (9) 有无消费请求?有,则继续(10);无,则转(16)
    (10) 此请求可满足?可满足,转(11);否,则阻塞,再转(10)
    (11) 确定产品位置
    (12) 此产品正被消费?是,则阻塞,再转(12);否,则转(13)
    (13) 进入临界区(请求同一产品的消费者之间互斥)
    (14) 消费产品,并判断是否应该释放产品所占缓冲区
    (15) 退出临界区,转(9)
    (16) 结束消费者线程
    (17) 生产者
    (18) 存在空缓冲区?有,则继续(19);无,则阻塞,再转(18)
    (19) 另一生产者在写?否,则转(20);是,则阻塞,再转(19)
    (20) 进入临界区(请求同一产品的生产者之间互斥)
    (21) 在缓冲区中为本线程产品分配空间
    (22) 退出临界区
    (23) 写入产品到分配的缓冲区空间中
    (24) 结束生产者线程
    (2)流程示意图

    详细设计:
    先定义生产者消费者数量和缓冲区大小,再设置生产者消费者操作完后的暂停时间SleepTime 3,然后设置缓冲区长度element buffer[N] 为10,将生产者序号ProCount、消费者序号ConCount初始化为0,最后定义程序开始时间的毫秒值StartTime初值为0。
    假设有多个生产者和多个消费者,它们共享一个含有n个存放单元有穷缓冲区Buffer,这是一个环形队列。其队尾指针Rear指向目前信息应存放位置(Buffer[Rear]),队首指针Front指向目前取出信息位置(Buffer[front])。生产者进程总是把信息存放在Buffer[Rear]中,消费者进程则总是从Buffer [Rear]中取出信息,缓冲区无数据则生产者进程结束。
    遵照以下规则:
    1)只要缓冲区有存放单元,生产者都可往其中存放信息;当缓冲区已满时,若任意生产者提出写要求,则都必须等候;
    2)只要缓冲区中有消息可取,消费者都可从缓冲区中取出消息;当缓冲区为空时,若任意消费者想取出信息,则必须等候;
    3)生产者们和消费者们不能同时读、写缓冲区。
    当消费者消费完缓冲区最后一个产品之后,进程结束。
    五、详细代码与调试
  2. 数据说明
    生产者消费者人数是随机的,每个生产者一次只能生产一个产品,消费者只能在缓冲区有数据时才能进行消费且一次只能消费一个产品。若缓冲区进程有操作,则生产者等待;若缓冲区为空,则消费者等待。缓冲区默认最大为10,信号灯mutex初值为1。
    六、实验总结与分析
    本次实验是关于生产者和消费者之间互斥和同步的问题。问题的实质是P,V操作,实验设一个共享缓冲区,生产者和消费者互斥的使用,当一个线程使用缓冲区的时候,另一个让其等待知道前一个线程释放缓冲区为止。在这个问题中,生产者不断的向缓冲区中写入数据,而消费者则从缓冲区中读取数据。生产者进程和消费者对缓冲区的操作是互斥,即当前只能有一个进程对这个缓冲区进行操作,生产者进入操作缓冲区之前,先要看缓冲区是否已满,如果缓冲区已满,则它必须等待消费者进程将数据取出才能写入数据,同样的,消费者进程从缓冲区读取数据之前,也要判断缓冲区是否为空,如果为空,则必须等待生产者进程写入数据才能读取数据。
    因为本程序允许有多个生产者和消费者存在,这就导致了一开始的结果中分不清到底是哪个生产者或消费者在工作,因此决定加上编号,逻辑就清晰多了。在程序运行时发现几个生产者线程提示信息缠在一起,经考虑后认定问题出在StartTime的赋值上,需要较大的数值间隔才能保证两个程序分开。
    为了协调发送和接收双方的通信,管道通信机制必须提供以下3方面的协调功能。
    (1)互斥:当一个进程正在对pipe文件进行读或写操作时,另一个进程必须等待。
    (2)同步:当写进程把一定数量的数据写入pipe文件后,便阻塞等待,直到读进程取走数据后,再把写进程唤
    (3)确认对方是否存在:只有确定对方已存在时,才能进行管道通信,否则会造成因对方不存在而无限制地等待。在这个问题当中,我们采用信号量机制进行进程之间的通信,设置两个信号量,空的信号量和满的信号量。在Linux系统中,一个或多个信号量构成一个信号量集合。使用信号量机制可以实现进程之间的同步和互斥,允许并发进程一次对一组信号量进行相同或不同的操作。每个P、V操作不限于减1或加1,而是可以加减任何整数。在进程终止时,系统可根据需要自动消除所有被进程操作过的信号量的影响
    生产者与消费者是一个与现实有关的经验问题,通过此原理举一反三可以解决其他类似的问题。 通过本实验设计,我对操作系统的P、V进一步的认识,深入的了解P、V操作的实质和其重要性。课本的理论知识进一步阐述了现实中的实际问题。虽然在实验中遇到了一些问题,但在同学的热心帮助下解决了。
    值得注意的是解决进程同步需要做的工作,如何利用信号量机制来解决进程同步问题等等,这些问题其实我们在学习理论知识时都是很少思考的,因为感触不深,所以学了一遍就过去了,但是在自己做实验时才会发现哪些地方是我需要考虑的,哪些地方是需要注意的,实验给了我实践的机会,给了我们理论结合实际的机会,从实验中可以学到很多东西,不仅仅是书本上的东西这么简单,更重要的是对待事情严谨的态度,对待任何事情都要一丝不苟,细节决定成败!