操作系统课程设计消费者和生产者问题源码解析

消费者生产者主要是就是PV操作的问题,但是其中还掺杂了线程问题。


1、首先要先明确一些知识点:

当一个进程启动的时候随即会创建一个主线程,那么在程序中还可以用CreateThread这个函数来创建进程内的新的线程为进程的运行提高效率。

这个函数的定义原形如下:

HANDLECreateThread(
 LPSECURITY_ATTRIBUTESlpThreadAttributes,//SD
 SIZE_TdwStackSize,//initialstacksize
 LPTHREAD_START_ROUTINElpStartAddress,//threadfunction
 LPVOIDlpParameter,//threadargument
 DWORDdwCreationFlags,//creationoption
 LPDWORDlpThreadId//threadidentifier
);
dwStackSize,为这个线程设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。程序在此处也用了主线程的栈大小


lpStartAddress指定了线程函数。但是必须以DWORD WINAPI 函数名 (LPVOID lpParam)这种形式来定义
DWORDdwCreationFlags表示是线程的标志,如果为0那么创建后立刻激活这个线程
LPDWORDlpThreadId 在这个变量里面保存新线程ID


在创建这个线程激活之后,这个线程的开始就是函数的第三个参数,这个参数应该是一个函数名,线程就开始于这个函数。函数名后面的参数便是调用这个函数的参数。


前面的一些字符常量的定义我已经在源码中给出了详细的注释

HANDLE Mutex;   //用于互斥
HANDLE FullSemaphore; //当缓冲区满的时候迫使生产者等待
HANDLE EmptySemaphore; //当缓冲区空的时候迫使消费者等待

int buffer[SIZE_OF_BUFFER];          //缓冲区是个循环队列

三个信号量的定义以及缓冲区的定义尤为重要,在之后的程序中创建互斥和同步信号量的时候,方法不同但是都返回一个句柄。句柄这个概念我也已经说过多次,就是创建 了一个对象之后返回这个对象的标识,以便于之后能够找到这个对象。


消费和生产的过程和我们上课时候讲的流程是一模一样的,唯一要注意的就是缓冲区的问题。

缓冲区设两个指针,第一个是in指针,第二个是out指针,两个指针分别对应于生产和消费。要注意的是, 程序已经规定了缓冲区的大小只有两个,并且采用循环数组实现缓冲区。

在生产者生产了资源之后,会把相应的资源放到缓冲区中,此时缓冲区存的是生产者此次生产的ID号码,在消费者从缓冲区取出资源的时候,会把数组中相应的数值重置为0代表已经取走,并且指针前移。



2、程序的主要流程:

先是进入主函数,然后创建用于保护临界区的互斥体,之后利用CreateSemaphore函数创建同步信号量,用于生产者和消费者在对缓冲区进行操作的时候进行协调。同步信号量包括两个,第一个是表示缓冲区是否还有空位,因为缓冲区在初始状态有两个空位,故此这个信号量的初始值也是2.另外一个信号量是表示缓冲区有几个资源,因为缓冲区初始化的时候 是没有资源的,所以赋初值为0.

之后设定生产者和消费者的个数,并且累计了生产者和消费者的总的线程数。

将每一个线程的句柄最后都存于一个数组内,便于管理。


之后根据生产者线程个数创建生产者线程,每一次创建一个线程,线程都立即激活,因为缓冲区个数为2,生产者个数为3,那么在每一次生产者线程创建的时候,激活生产函数,将 EmptySemaphore信号量在P操作的时候消耗减去1,等到为0的时候,此时缓冲区已满不能再继续生产。当再生产第三个生产线程的时候,信号量变为-1,此时第三个线程已经阻塞不能执行。再次期间,不但两个生产线程在执行,并且两个生产线程是霸占了现在的临界区的,也就是缓冲区,但是主进程中的消费者线程也在建立并且激活。但是在生产者没生产完之前,所有的消费者都被阻塞在了FullSemaphore这个同步信号量上,即使一个生产者生产完资源通过ReleaseSemaphore(FullSemaphore, 1, NULL);激活一个消费者线程,那么被激活的这个也不能继续执行,因为还有一个生产者尚在临界区,消费者不能获得临界区的互斥体,得等所有的生产者生产完毕,并且退出临界区,这个时候消费者才能够进入临界区进行消费资源。


在这里要提醒大家的是,只要进程不被杀死,那么在进程中的线程也会一直在运行。所以在消费者线程运行的时候自然也会阻塞一些生产者进程,当 消费者消费完之后,退出临界区,ReleaseSemaphore(EmptySemaphore, 1, NULL);就会使用这个函数激活生产者进程,发出信号告诉他此时缓冲区已经有空位。当然,生产者也必须在消费者完成所有工作的前提下在进行工作,只要你手动停止整个进程为止。因为临界区在这里是互斥的,任何时候都只有消费者或者生产者其中一个进入这个区域,即使同为生产者,两个以上也只有其中的一个能够进入缓冲区。


3、一些改动


(1)EmptySemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER, NULL),把这个信号量的初值改变,也就是改变了缓冲区在初始化的时候默认里面没有空位,那样生产者生产了资源之后无法放入,消费者要取出资源还无法取出,生产者和消费者互相依赖彼此来唤醒,所以造成了死锁的情况。

(2)完全可以用 CreateSemaphore代替CreateMutex。一个是修改创建信号量的语句:Mutex = CreateSemaphore(NULL, 1, SIZE_OF_BUFFER, NULL),还有一处修改就是改变出临界区的时候增加信号量的语句:ReleaseSemaphore(Mutex, 1, NULL)。这是课堂上讲的东西,我不愿在废话。


最后源码如下:

#include<windows.h>
#include<iostream>
#include<cstring>
using namespace std;
const unsigned short SIZE_OF_BUFFER = 2;  //缓冲区长度
unsigned short ProductID = 0;         //产品号
unsigned short ConsumeID = 0;         //将被消耗的产品号
unsigned short in = 0;				//产品进入缓冲区时的缓冲区下标
unsigned short out = 0;              //产品出缓冲区的时候缓冲区的下标
int buffer[SIZE_OF_BUFFER];          //缓冲区是个循环队列
bool p_ccontinue = true;             //控制程序结束
HANDLE Mutex;   //用于互斥
HANDLE FullSemaphore; //当缓冲区满的时候迫使生产者等待
HANDLE EmptySemaphore; //当缓冲区空的时候迫使消费者等待

DWORD WINAPI Producer(LPVOID);       //生产者进程
DWORD WINAPI Consumer(LPVOID);		//消费者进程
int main()
{
	Mutex = CreateMutex(NULL, FALSE, NULL);   //创建互斥信号量
	/*
	找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。CreateMutex()函数可用来创建一个有名或无名的互斥量对象
	HANDLE CreateMutex(
	LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针
	BOOLbInitialOwner, // 初始化互斥对象的所有者
	LPCTSTRlpName // 指向互斥对象名的指针
);
	*/
	EmptySemaphore = CreateSemaphore(NULL, SIZE_OF_BUFFER, SIZE_OF_BUFFER, NULL); //创建空不取同步信号量
	/*
	HANDLE CreateSemaphore(
    LPSECURITY_ATTRIBUTESlpSemaphoreAttributes, // SD
    LONGlInitialCount, // initial count
    LONGlMaximumCount, // maximum count
    LPCTSTRlpName// object name	
	);
	创建一个新的信号量,如执行成功,返回信号量对象的句柄;零表示出错
	lInitialCount Long,设置信号量的初始计数。可设置零到lMaximumCount之间的一个值
	lMaximumCount Long,设置信号量的最大计数
	lpName String,指定信号量对象的名称。
	它是提供一个计数值,它允许在这个计数值之内,
	任何执行到WaitForSingleObject的Thread都不会停下来,
	而且每执行WaitForSingleObject一次,计数值就减一,
	当计数值变成0时,该Semaphore才会处於UnSignal的状态,
	而某个Thread ReleaseSemaphore时,便会将计数值增加,
	以便其他的Thread或本身可得Signal的讯号,而使WaitForSingleObject停止等待。
	*/
	FullSemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER, NULL);   //创建满不放信号量
	const unsigned short PRODUCERS_COUNT = 3;    //生产者个数
	const unsigned short CONSUMERS_COUNT = 1;    //消费者个数
	const unsigned short THREADS_COUNT = PRODUCERS_COUNT + CONSUMERS_COUNT;  //总的线程数

	HANDLE hThreads[THREADS_COUNT];		//各路线程的handle
	DWORD producerID[PRODUCERS_COUNT];   //生产者和消费者的标识符
	DWORD consumerID[CONSUMERS_COUNT]; 
	
	//创建生产者线程
	for(int i = 0; i < PRODUCERS_COUNT; i++)
	{
		hThreads[i] = CreateThread(NULL, 0, Producer, NULL, 0, &producerID[i]);
		if(hThreads[i] == NULL) return -1;
	}

	//创建消费者线程
	for(int i = 0; i < CONSUMERS_COUNT; i++)
	{
		hThreads[i + PRODUCERS_COUNT] = CreateThread(NULL, 0, Consumer, NULL, 0, &consumerID[i]);
		if(hThreads[i + PRODUCERS_COUNT] == NULL) return -1;
	}
	while(p_ccontinue)
	{
		if(getchar())
		{
			p_ccontinue = FALSE;
		}
	}
	return 0;
}


//生产函数
void Produce()
{
	cout << endl << "Producing" << ++ ProductID << ".......";
	cout << "succeed"<< endl;
}

//生产结果放入缓冲区
void Append()
{
	cerr << "Appending a product ... ";
	buffer[in] = ProductID;
	in = (in + 1) % SIZE_OF_BUFFER;
	cerr << "Succeed" << endl;

	//输出缓冲区当前状态
	for(int i = 0; i < SIZE_OF_BUFFER; i++)
	{
		cout << i << ":" << buffer[i];
		if(i == in) cout << "<----生产";
		if(i == out) cout << "<-----消费";
		cout << endl;
	}
}

//从缓冲区取出
void Take()
{
	cerr << "Taking a product . . .";
	ConsumeID = buffer[out];
	buffer[out] = 0;
	out = (out + 1) % SIZE_OF_BUFFER;
	cerr << "SUcceed" << endl;
	//输出缓冲区当前状态
	for(int i = 0; i < SIZE_OF_BUFFER; i++)
	{
		cout << i << ":" << buffer[i];
		if(i == in) cout << "<----生产";
		if(i == out) cout << "<-----消费";
		cout << endl;
	}
}


void Consume()
{
	cout << "Consuming " << ConsumeID << "...";
	cout << "Succeed" << endl;
}



DWORD WINAPI Producer(LPVOID lpPara)
{
	while(p_ccontinue)
	{
		//两个P操作
		WaitForSingleObject(EmptySemaphore, INFINITE); 
		//判断缓冲区此时是否有空间
		WaitForSingleObject(Mutex, INFINITE);
		//保护临界区,互斥
		Produce();
		Append();
		Sleep(500);
		//两个V操作
		ReleaseMutex(Mutex);
		ReleaseSemaphore(FullSemaphore, 1, NULL);
	}
	return 0;
}

DWORD WINAPI Consumer(LPVOID lpPara)
{
	while(p_ccontinue)
	{
		//两个P操作
		WaitForSingleObject(FullSemaphore, INFINITE);
		WaitForSingleObject(Mutex, INFINITE);
		Take();
		Consume();
		Sleep(500);
		ReleaseMutex(Mutex);
		ReleaseSemaphore(EmptySemaphore, 1, NULL);
	}
	return 0;
}





你可能感兴趣的:(操作系统课程设计消费者和生产者问题源码解析)