1 设计题目与要求 1.1题目: 用多线程同步方法解决生产者-消费者问题(Bounded - Buffer Problem) 1.2目的: 通过研究Linux的线程机制和信号量实现生产者消费者问题的并发控制。 1.3说明: 有界缓冲区内设有20个存储单元,放入/取出的数据项设定为1-20这20个整型数。 1.4要求: 1.4.1 每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的全部内容、当前指针位置和生产者/消费者线程的标识符。 1.4.2 生产者和消费者各有两个以上。 1.4.3 多个生产者或多个消费者之间须共享对缓冲区进行操作的函数代码。 2 总的设计思想 2.1问题描述 生产者-消费者问题是一个经典的进程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制。在同一个进程地址空间内执行的两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来。 2.2 设计思想 Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork。 信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。 pthread提供的程序并发控制有: pthread_create() /*创建新的进程*/ pthread_join() /*等待的进程的结束*/ sem_init() /*信号量初始化*/ sem_wait(); /*信号量wait操作*/ sem_post(); /*信号量signal操作*/ phtread_mutex_init() /*互斥量初始化*/ phtread_mutex_lock() /*互斥量P操作*/ pthread_mutex_unck() /*互斥量V操作*/ 3 系统平台、语言、工具等 系统平台:linux操作系统 语言: linux下的C语言编程 工具: Linux主机 以及终端 3.1硬件及软件支持环境 硬件要求不高,普通PC机即可。本程序的开发利用需要PC 装有linux系统,或者linux终端,他是开发核运行程序的环境。Linux 并发程序支持库pthread , pthread 是符合POSIX标准的多线程库。 3.2开发环境 vi 文本编辑 gcc c语言编译程序 4 数据结构与模块说明 4.1 数据结构及功能 本程序中的数据结构比较简单,采用长度为20的数组来存放生产者生产的物品。用数组的下标来表示当前指针的位置。 主程序: int main() { pthread_t p1,p2,p3…..pn,c1,c2,c3……cn; int ret,i; sem_init(&empty,0,MAX); //初始化信号量 sem_init(&full,0,0); sem_init(&mutex,0,1); srand(time(0)); for(i=0;i<MAX;i++) buffer[i]=0; 创建线程: ret = pthread_create(&pn,NULL,producer,(void*)n); //创建生产者线程 if(ret) { printf("create producern error!"); abort(); } ret =pthread_create(&cn,NULL,consumer,(void*)n); //创建消费者线程 if(ret) { printf("create consumern error !"); abort(); } 生产者线程: void *producer(void *threadid) { int i=0; while(1) { sem_wait(&empty); sem_wait(&mutex); ………… sem_post(&mutex); sem_post(&full); } } 消费者线程: void *consumer(void *threadid) { int i=0; while(1) { sem_wait(&full); sem_wait(&mutex); ………… sem_post(&mutex); sem_post(&empty); } } 4.2 流程图
5 用户名、源程序名、目标程序名及源程序 主机IP:192.168.1.200 目录:.../home/j030126/ 用户名:j030126 源程序名:pro_cus.c 目标程序名:pro_cus 源程序 #include <semaphore.h> #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <time.h> #define MAX 20 int buffer[MAX]; int size=0; sem_t empty, full, mutex; void *producer(void* threadid); void *consumer(void* threadid);
int main() { pthread_t p1,p2,p3,c1,c2,c3; int ret,i; sem_init(&empty,0,MAX); sem_init(&full,0,0); sem_init(&mutex,0,1); srand(time(0)); for(i=0;i<MAX;i++) buffer[i]=0;
ret =pthread_create(&p1,NULL,consumer,(void*)1); if(ret) { printf("create consumer1 error !"); abort(); } ret = pthread_create(&c1,NULL,producer,(void*)1); if(ret) { printf("create producer1 error!"); abort(); } ret =pthread_create(&p2,NULL,consumer,(void*)2); if(ret) { printf("create consumer2 error !"); abort(); } ret = pthread_create(&c2,NULL,producer,(void*)2); if(ret) { printf("create producer2 error!"); abort(); } ret =pthread_create(&p3,NULL,consumer,(void*)3); if(ret) { printf("create consumer3 error !"); abort(); } ret = pthread_create(&c3,NULL,producer,(void*)3); if(ret) { printf("create producer3 error!"); abort(); } pthread_join(p1,NULL); pthread_join(c1,NULL); pthread_join(p2,NULL); pthread_join(c2,NULL); pthread_join(p3,NULL); pthread_join(c3,NULL); }
void *producer(void *threadid) { int i=0; while(1) { sleep(1); sem_wait(&empty); sem_wait(&mutex); buffer[(size++)%MAX]=1; for(i=0;i<MAX;i++) printf("%d",buffer[i]); printf("\nThis is Product %d thread.\n",(int)threadid); sem_post(&mutex); sem_post(&full); sleep(1); } }
void *consumer(void *threadid) { int i=0; while(1) { sleep(rand()%2+1); sem_wait(&full); sem_wait(&mutex); /*buffer[(--size+MAX)%MAX]=0;*/ for(i=0;i<MAX;i++) { if(buffer[i]==1) {buffer[i]=0;break;} printf("%d",buffer[i]); } for(;i<MAX;i++) printf("%d",buffer[i]); printf("\nThis is Customer %d thread.\n",(int)threadid); sem_post(&mutex); sem_post(&empty); sleep(rand()%2+1); } }
6 运行结果与运行情况 本程序运行是随机的,每次运行的结果是不一样的,生产者线程和消费者线程是抢占式的运行,生产者线程和消费者线程数分别为3,生产的产品按顺序存放在存储单元,如果存储单元有产品(显示为1,无产品则为0),那么消费者按顺序消费最先生产出来的产品。这里存在潜在的竞争条件,所谓竞争条件就是这样一种情况:多个线程对数据产生的作用要依赖于线程的调度顺序的。当两个线程竞相访问同一数据时,就会发生竞争条件。
7 调试报告 7.1调试记录 本问题主要是实现进程同步和竞争的问题。因此程序最主要的是解决进程间的同步问题。为了清晰地表示出他们的调度过程,需要在调度的同时显示他们的标识符。缓冲区内容和当前指针位置也要显示以帮助理解,还可以当程序出错时帮助分析错误原因。另外题目还要求至少要有两个生产者、两个消费者。 在这个实例中,首先定义了一个大小为20的公共缓冲区,也就是临界资源,然后的buffer[MAX]便是缓冲区中产品的数目,初始化为0。producer函数是生产者函数,produce_item(&item);是指生产者生产出来一个产品,但是这时候并没有对缓冲区进行操作。在这里一开始出现了些小问题,就是在定义变量的时候忘记了格式,所以开始调试程序的时候出现很多提示错误,后来再仔细的查看了资料后,才纠正了错误的定义方式。if (count==N) sleep();是测试语句,如果生产出来的产品数和缓冲区大小相等时,生产者就进入睡眠状态。如果不等,产品就放入缓冲区内,并且产品数增加1。if (count==1) wakeup(consumer);这条语句的意思是,如果上一次操作时产品的数目为0,消费者已经进入了睡眠状态,而现在生产者又生产出来一个产品,缓冲区内不为空,这时把消费者唤醒。消费者函数也是同样的道理,只不过一个是取,另一个是放。我们可以看到,这里存在潜在的竞争条件,所谓竞争条件就是这样一种情况:多个线程对数据产生的作用要依赖于线程的调度顺序的。当两个线程竞相访问同一数据时,就会发生竞争条件。由于时间片的原因,一个线程可以在任意一个时刻打断其他线程,因此数据可能会被破坏或者被错误地解释。还有在编写代码时出现了许多常规错误如函数名输入错误、变量没有提前声明就使用、缺少头文件等。在整个程序的调试过程中最让我头疼的一个问题就是创建线程时数组的指针出了错,运行时提示有段错误,这让我一开始根本不知道是怎么回事,在求助了同学的基础上,我查阅了下资料,发现很可能是在指针上出现了问题,仔细研究了程序后,觉得可能是指针在指向地址时由于指向冲突而引起的,所以我把原来的生产者、消费者线程标识符pro、con分别改为proid、conid,以跟生产者和消费者线程子函数名 pro、con 区别开来,避免指针地址同名。然后再次运行程序,终于可以顺利执行。 7.2自我评析和总结 在动手实现该程序的过程中,加深了对信号量、线程、等待与同步等重要概念的理解,也提高了自己使用linux编写C程序的能力。同时,也锻炼了自己动手解决问题,查阅相关资料的能力.在具体编写代码时,通过实践也认识到养成良好的编码习惯的重要性.正确的代码书写习惯和格式,可以提高程序的可读性、可调试性。 8 参考文献 《操作系统概念》 Abraham silberschatz Peter Baer Galvin Greg Gagne 编 高等教育出版社 《操作系统基础》 屠立德 等编 清华大学出版社