操作系统实验课程报告
课题: 消费者与生产者实验
姓 名 张涛
学 院 计算机学院
班 级 2011211311
学 号 2011211419
2013年 12月 14日
1.实验目的:
1) 理解线程同步的思想和方法,学会用线程同步解决临界区问题,本次实验解决生产者消费者问题
2了解windows系统或linux系统下中信号量的使用方法。
2.实验预备内容
(1) 阅读Linux的sched.h源码文件,加深对进程管理概念的理解。
这个文件长达2616行,这里截取第1221~1548行抄录在实验报告最后,即结构体task_struct,地位相当于PCB。
下面对几个比较重要的参数,结合本人的了解 以及 网上查阅的资料 做一点解释。
中括号内的数字为代码行号,下同。
volatile long state:【1222】进程状态字,表示进程当前的状态(运行、就绪、等待、僵死、暂停、交换),分别对应已定义好的常量;
TASK_RUNING:正在运行或可运行状态;
TASK_INTERRUPTIBLE:可打断睡眠状态;
TASK_UNINTERRUPTIBLE:不可打断睡眠状态;
TASK_ZOMBLE:僵死状态;
TASK_STOPPED:暂停状态;
交换状态。
void *stack:【1223】进程所使用的栈空间;
unsigned int flags:【1225】进程标志(创建、关闭、跟踪、被跟踪、内核dump等),同样对应已定义好的常量;
unsigned int rt_priority:【1237】表示本进程的实时优先级;
const struct sched_class *sched_class、struct sched_entity se:【1239,1240】分别是调度类和调度实体,这两个结构包含了用于任务调度的完整的信息(进程信息、调度策略等);
unsigned int policy:【1260】进程的调度策略标志,有三种调度标志:
SCHED_OTHER :普通进程的调度策略,基于优先权的轮转法;
SCHED_FIFO:实时进程的调度策略,基于先进先出的算法;
SCHED_RR:实时进程的调度策略,基于优先权的轮询法。
struct list_head tasks:【1274】任务队列,为一双向循环链表;
int pdeath_signal:【1282】父进程终止时产生的信号;
pid_t pid:【1294】进程标识符,操作系统每创建一个新的进程就要为这个新进程分配一个进程控制块(PCB),系统内核通过pid区分这些进程的;
struct task_struct *real_parent:【1307】本进程的父进程的PCB;
struct list_head children:【1312】本进程的子进程列表;
struct list_head ptraced:【1321】本进程正在使用ptrace监视的进程列表;
struct thread_struct thread:【1375】本进程下属的线程集;
struct signal_struct *signal、struct sighand_struct *sighand:【1383,1384】分别是进程运行时产生的信号以及信号处理模块。
(2) 阅读Linux的pthread.h源码文件,分析线程的创建过程。
pthread接口说明
#include
1、创建
int pthread_create( pthread_t *tid, const pthread_attr_t *attr, void *(* func) (void *), void *arg );
attr: 线程属性包括:优先级、初始栈大小,是否应该成为一个守护线程。
缺省设置,NULL
后面是线程要执行的函数和参数
成功返回 0
2、等待一个给定线程终止
int pthread_join( pthread_t tid, void **status);等待线程结束 critiction 可以在进程中使用,mutex只可在进程中使用
statues返回等待线程的返回值
multiple definition of `__dso_handle'/usr/lib/gcc/i486-linux-gnu/4.4.3/crtbegin.o:(.data+0x0): first defined herethreadTest: In function `_init':(.init+0x0): multiple definition of `_init'/usr/lib/gcc/i486-linux-gnu/4.4.3/../../../../lib/crti.o:(.init+0x0): first defined here/tmp/cchm2SmY.o:(.data+0x0): multiple definition of `flag'threadTest:(.data+0x8): first defined here/tmp/cchm2SmY.o: In function `threadMe
3、得到自身的pid
pthread_t pthread_self(void);
4、pthread_detach函数
int pthread_detach( pthread_t pid );
把指定的线程转变为脱离状态
一个线程或者是可汇合的(joinable,缺省值),或者是脱离的(detached)。当一个可汇合的线程终止时,它的线程ID和退出状态将留到另一个线程对它调用pthread_join。脱离线程却象守护进程:当它们终止的时,所有相关资源都被释放,我们不能等待它们终止。如果一个线程需要知道另一个线程什么时候终止,那就最好好吃第二个线程的可汇合状态。
本函数通常由想让自己脱离的线程调用,如下语句
pthread_detach( pthread_self() );
5、终止一个线程
void pthread_exit( void *statue );
指针sttus不能指向局部于调用对象,因为线程终止时这样的对象也消失
(3) 阅读Linux的semaphore.h源码文件,分析线程的创建过程。
typedef struct sem_t_ * sem_t;
PTW32_DLLPORT int __cdecl sem_init (sem_t * sem,
int pshared,
unsigned int value);
PTW32_DLLPORT int __cdecl sem_destroy (sem_t * sem);
PTW32_DLLPORT int __cdecl sem_trywait (sem_t * sem);
PTW32_DLLPORT int __cdecl sem_wait (sem_t * sem);
PTW32_DLLPORT int __cdecl sem_post (sem_t * sem);
PTW32_DLLPORT int __cdecl sem_post_multiple (sem_t * sem,
int count);
PTW32_DLLPORT int __cdecl sem_close (sem_t * sem);
PTW32_DLLPORT int __cdecl sem_unlink (const char * name);
int sem_init(sem_t *sem,int pshared,unsigned int value)。用于信号量的初始化,第一个参数为信号量的名称,第二个参数为信号量的共享属性,一般设为0(不共享),第三个参数为信号量的初始值。
int sem_wait(sem_t *sem)。相当于上面的wait()操作,作用是当信号量的值大于0时,给信号量的值减1,否则会阻塞直至信号量的值大于0。它是一个原子操作。
int sem_post(sem_t *sem)。相当于上面的signal()操作,作用是给信号量的值加1。它也是一个原子操作。
******************************************************************************/
3.实验环境
此实验采用的是Win8下Microsoft Visual Stdio 2012工程。由于系统不包含semaphore.h,sched.h及pthread.h头文件,所以这些头文件都是从网上FTP资源下载后,加入工程的。
程序中加入#pragma comment(lib, "pthreadVC2.lib")来调用pthread链接库。
文件根目录下已下载pthreadVC2.dll
******************************************************************************/
4.实验步骤
A.设计思路
这个问题是进程同步的经典问题之一,基本思路是设置三个信号量:mutex信号量,用于控制生产者和消费者对于缓冲区的互斥访问;empty信号量,记录当前为空的缓冲区的数目,初始化为所有缓冲区的数目BUF_SIZE;full信号量,记录当前已满的缓冲区的数目,初始化为0。
******************************************************************************/
一个buffer,一个生产者,一个消费者
(1)规则
只有buffer为空才能put;只有buffer中有数据才能get;不允许多个put操作同时进行;不允许多个
get操作同时进行。
这时buffer变成了临界资源,消费者之间需要互斥地使用,生产者之间也需要互斥地使用,消费者和生产者之间也需要互斥地使用,这时设置两个信号量
s1,s2实现同步,例外设置S信号,代表buffer这种临界资源,用于互斥,称之为互斥信号量。
(2)实现流程
<生产者>p(s1)"判断buffer是否空"
p(s)
"是否可进行put,是否有其他进程占用
buffer"
putv(s)"释放buffer,让其他进程可以使用"v(s2)
"给消费者进程释放一个buffer中有数据的
信号"
<消费者>p(s2)"判断是否有数据"
p(s)"是否可进行get,是否有其他进程占用"
getv(s)"释放buffer,让其他进程可以使用"v(s1)
"给生产者释放一个buffer为空的信号"通过3个信号量,实现了消费者和生产者之间同步关系,也保证了在某个时刻只有一个进程使用临界资源buffer。
生产者进程的代码结构描述如下:
while(true) { nextp=produce();wait(empty);wait(mutex);put(nextp);signal(mutex);signal(full); }
消费者进程的代码结构描述如下:
while(true) { wait(full);wait(mutex);nextc=get();signal(mutex);signal(empty);consume(nextc); }
这里两个wait语句的次序并不能调换,这是因为如果将两个wait操作即wait(full)和wait(mutex)互换位置,或者将release(mutex)与release(full)互换位置,当缓冲区存满产品时,生产者又生产了一件产品,它欲向缓冲区存放时将在empty上等待,但它已经占有了使用缓冲区的权利。这时消费者要取产品时将停留在mutex上得不到使用缓冲区的权利,导致生产者等待消费者取走产品,而消费者却在等待生产者释放使用缓冲区的权利,这种相互等待永远结束不了。因此进程将会发生死锁。
******************************************************************************/
B.实验代码分析
生产者:
/*
* Producer
* Produce items and try to put it into the buffer
* If the buffer is full, waiting
* When produced, we output like that: Producer 2 produced 222
* When successfully put, we output: Producer 2 have put product 222 into the buffer after 100 milliseconds
* The milliseconds is the timespan from the producer produced the item
* When the buffer is full but the producer put the item, an error occurred
* (No way it will happen...)
*/
#define PTIMESPANMAX 5000
void* producer(void* params)
{
int id=*(int *)params;
while(true)
{
SLEEP(rand()%PTIMESPANMAX);
buffer_item rnd=rand();
long begin, end;
CLOCK(begin);
printf("%ld:\t Producer %d\t produced %d\n", (long)(begin-threadStart), id, rnd);
sem_wait(&empty);
pthread_mutex_lock(&mutex);
(CLOCK(end), insert_item(rnd))
? printf("%ld:\t Producer %d\t have put %d\t after %d\t milliseconds\n",end-threadStart, id, rnd, end-begin)
: printf("%ld:\t Producer %d\t Report Error!\n", end-threadStart, id);
pthread_mutex_unlock(&mutex);
sem_post(&full);
}
}
******************************************************************************/
消费者:
/*
* Consumer
* Try to consume items from the buffer
* If the buffer is empty, waiting
* When try to consume, we output like that: Consumer 3 want to consume
* When successfully consumed, we output: Consumer 3 consumed 222 after 100 milliseconds
* The milliseconds is the timespan from the consumer try to consume the item
* When the buffer is empty but the consumer consumed the item, an error occurred
* (No way it will happen...)
*/
#define CTIMESPANMAX 5000
void * consumer(void* params)
{
int id=*(int *)params;
while(true)
{
SLEEP(rand()%CTIMESPANMAX);
long begin, end;
CLOCK(begin);
printf("%ld:\t Consumer %d\t want to consume\n", begin-threadStart, id);
sem_wait(&full);
pthread_mutex_lock(&mutex);
buffer_item item=buffer[currentIndex];
(CLOCK(end), remove_item(&item))
? printf("%ld:\t Consumer %d\t consumed %d\t after %d\t milliseconds\n", end-threadStart, id, item, end-begin)
: printf("%ld:\t Consumer %d\t Report Error!\n", end-threadStart, id);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
}
******************************************************************************/
主函数:
int main(int argc, char* argv[])
{
srand(unsigned int(time(0)));
// Init mutex, full and empty
pthread_mutex_init(&mutex, NULL);
sem_init(&full, 0, BUFFER_SIZE);
sem_init(&empty, 0, BUFFER_SIZE);
int i=0;
// e... No item in the buffer now, so full is "full"
for(i=0; i<BUFFER_SIZE; i++)
sem_wait(&full);
int sleepTime=0;
int producerCount=0;
int consumerCount=0;
printf("How long to sleep before terminating: ");
scanf_s("%d", &sleepTime);
printf("The number of producer threads: ");
scanf_s("%d", &producerCount);
printf("The number of consumer threads: ");
scanf_s("%d", &consumerCount);
//Init threadStart
CLOCK(threadStart);
// Create producer threads
for(i=0; i
{
pthread_t pid;
pthread_create(&pid, NULL, producer, (void *)&i);
SLEEP(1);
}
// Create consumer threads
for(i=0; i
{
pthread_t pid;
pthread_create(&pid, NULL, consumer, (void *)&i);
SLEEP(1);
}
//Just wait and then... end
SLEEP(sleepTime);
printf("End of time\n");
return 0;
}
******************************************************************************/
C.实验运行结果:
1.程序初始化格式:
******************************************************************************/
2.程序运行结果:
生产者:producer 0 1 2
消费者:consumer 0 1 2
生产者生产后将数据put到buffer中去
消费者在申请mutex(want to consume)后,即可消费。
由结果看出,三个消费者和三个生产者一直处于相互互斥工作状态,并未出现死锁,拥堵等异常状态。程序运行成功。
5.实验总结
通过这次实验学会了使用信号量机制来控制访问临界区资源,同时要防止deadlock、starvation的产生。本次实验使用的是pthread的接口,熟悉了对这些接口的使用,通过学习,也提高了自己的学习能力。理解了线程同步的概念,掌握了基本方法,加深了课上所学知识的理解和掌握。程序运行时未出现特别错误,只是在头文件下载时花了很长工夫才找到接口资源。。。抱怨一下ftp链接的不稳定。总的来说,这次实验收获很大,学以致用,巩固了课堂知识。
¥29.8
¥9.9
¥59.8