【生产者-消费者问题】Windows线程的互斥与同步

一、实验目的

1、回顾操作系统进程、线程的有关概念,加深对Windows线程的理解;

2、了解互斥体对象,利用互斥与同步操作编写生产者-消费者问题的并发程序,加深对P (即semWait)、V(即semSignal)原语以及利用P、V原语进行进程间同步与互斥操作的理解。

二、流程图

 

 三、代码实现

#include <windows.h> 
#include <iostream> 
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()
{
	//创建各个互斥信号 
	//注意,互斥信号量和同步信号量的定义方法不同,互斥信号量调用的是CreateMutex函数
	//同步信号量调用的是 CreateSemaphore 函数,函数的返回值都是句柄。
	Mutex = CreateMutex(NULL, FALSE, NULL);
	EmptySemaphore = CreateSemaphore(NULL, SIZE_OF_BUFFER, SIZE_OF_BUFFER, NULL);
	//EmptySemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER-1, NULL); 
                                                            //(第二次修改)
	FullSemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER, NULL);
	//调整下面的数值,可以发现,当生产者个数多于消费者个数时, 
	//生产速度快,生产者经常等待消费者;反之,消费者经常等待 
	const unsigned short PRODUCERS_COUNT = 3;				//生产者的个数 
	const unsigned short CONSUMERS_COUNT = 1;				//消费者的个数 
	//const unsigned short PRODUCERS_COUNT = 1;				//生产者的个数(第一次修改)
	//const unsigned short CONSUMERS_COUNT = 3;				//消费者的个数(第一次修改)
	//总的线程数		
	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[PRODUCERS_COUNT + i] = CreateThread(NULL, 0, Consumer, NULL, 0, &consumerID[i]);
		if (hThreads[i] == NULL)
			return -1;
	}
	while (p_ccontinue) {
		if (getchar()) {									//按回车后终止程序运行 
			p_ccontinue = false;
		}
	}
	return 0;
}
 
void Produce()												//生产一个产品。简单模拟了一下,仅输出新产品的 ID 号
{
	std::cout << std::endl << "Producing " << ++ProductID << " ... ";
	std::cout << "Succeed" << std::endl;
}
 
void Append()												//把新生产的产品放入缓冲区
{
	std::cerr << "Appending a product ... ";
	buffer[in] = ProductID;
	in = (in + 1) % SIZE_OF_BUFFER;
	std::cerr << "Succeed" << std::endl;
	//输出缓冲区当前的状态 
	for (int i = 0;i < SIZE_OF_BUFFER;++i) {
		std::cout << i << ": " << buffer[i];
		if (i == in) std::cout << " <-- 生产";
		if (i == out) std::cout << " <-- 消费";
		std::cout << std::endl;
	}
}
 
void Take()													//从缓冲区中取出一个产品
{
	std::cerr << "Taking a product ... ";
	ConsumeID = buffer[out];
	buffer[out] = 0;
	out = (out + 1) % SIZE_OF_BUFFER;
	std::cerr << "Succeed" << std::endl;
	//输出缓冲区当前的状态 
	for (int i = 0;i < SIZE_OF_BUFFER;++i) {
		std::cout << i << ": " << buffer[i];
		if (i == in) std::cout << " <-- 生产";
		if (i == out) std::cout << " <-- 消费";
		std::cout << std::endl;
	}
}
 
void Consume()												//消耗一个产品
{
	std::cout << "Consuming " << ConsumeID << " ... ";
	std::cout << "Succeed" << std::endl;
}
 
DWORD WINAPI Producer(LPVOID lpPara)						//生产者
{
	while (p_ccontinue) {
		WaitForSingleObject(EmptySemaphore, INFINITE);		//p(empty); 
		WaitForSingleObject(Mutex, INFINITE);				//p(mutex);
		Produce();
		Append();
		Sleep(1500);
		ReleaseMutex(Mutex);								//V(mutex);
		ReleaseSemaphore(FullSemaphore, 1, NULL);			//V(full);
	}
	return 0;
}
 
DWORD WINAPI Consumer(LPVOID lpPara)						//消费者
{
	while (p_ccontinue) {
		WaitForSingleObject(FullSemaphore, INFINITE);		//P(full); 
		WaitForSingleObject(Mutex, INFINITE);				//P(mutex);
		Take();
		Consume();
		Sleep(1500);
		ReleaseMutex(Mutex);								//V(mutex);
		ReleaseSemaphore(EmptySemaphore, 1, NULL);			//V(empty); 
	}
	return 0;
}

四、实验结果与分析

1、修改前

 

        设置的缓冲区大小为2,初始情况下生产者和消费者共享空的缓冲区(in指针和out指针都指向0号缓冲区)。当1号生产者生产出1号产品之后,将产品号存入0号缓冲区,in指针向后移动一位,指向1号缓冲区。之后2号生产者再次生产2号产品后,将产品号存入1号缓冲区,in指针向后移动一位,由于缓冲区采取循环队列的数据结构,因此下一次in指针将指向0号缓冲区,此时缓冲区已满,因此3号及之后的生产者想要生产只能原地等待消费者去消费产品。此时1号消费者消费1号产品之后,将0号缓冲区清空,out指针向后移动一位,指向1号缓冲区。之后2号消费者继续消费2号产品,将1号缓冲区清空,out指针向后移动一位,指向0号缓冲区,此时缓冲区已空,因此3号及之后的消费者想要消费只能原地等待生产者去生产产品。以此类推可以分析出所有的结果。

2、第一次修改

        调整PRODUCERS_COUNT和CONSUMERS_COUNT的值可以发现,当消费者数目大于生产者时,消费速度快,消费者经常等待生产者。

3、第二次修改

        将信号量修改后结果无输出。初始状态下buff空的信号量应该是等于buffer的大小的,若按题目要求修改后,buffer空信号量初始化变为0,这就说明buffer一开始是满的,但是实际上buffer却应该是空的。所以生产者P(EmptySemaphore)时就会一直被阻塞,而此时因为生产者没生产,所以消费者P(FullSemaphore)时也会一直被阻塞,从而形成死锁而无法输出。

五、问题讨论

1、仔细阅读源程序,找出创建线程的WINDOWS API函数,回答下列问题:说明线程的第一个执行函数是什么(从哪里开始执行)?它位于创建线程的API函数的第几个参数中?

答:线程的第一个执行函数是创建线程的函数CreateThread(),从Producer()/Consumer()开始执行;位于创建线程的API函数的第三个参数中。

2、第一次修改代码时调整PRODUCERS_COUNT和CONSUMERS_COUNT的值,使得消费者数目大于生产者,再重新编译运行程序,列出输出结果,能从中得出什么结论?

答:调整PRODUCERS_COUNT和CONSUMERS_COUNT的值可以发现,当消费者数目大于生产者时,消费速度快,消费者经常等待生产者。

3、第二次修改代码时按照实验说明修改信号量EmptySemaphore的初始化方法,再重新编译运行程序,列出输出结果并观察与上一次有何不同?

答:将信号量如上修改,结果无输出。初始状态下buff空的信号量应该是等于buffer的大小的,若按题目要求修改后,buffer空信号量初始化变为0,这就说明buffer一开始是满的,但是实际上buffer却应该是空的。所以生产者P(EmptySemaphore)时就会一直被阻塞,而此时因为生产者没生产,所以消费者P(FullSemaphore)时也会一直被阻塞,从而形成死锁而无法输出。

4、根据实验结果并查看MSDN(Microsoft Developer Network),回答下列问题:

① CreateMutex中有几个参数?各代表什么含义?

答:CreateMutex共含有三个参数:

(ⅰ)lpMutexAttributes SECURITY_ATTRIBUTES:表示使用不允许继承的默认描述符;

(ⅱ)bInitialOwner BOOL:希望进程立即拥有互斥体,则设为TRUE;

(ⅲ)lpName String:指定互斥体对象的名字;

② CreateSemaphore中有几个参数?各代表什么含义?信号量的初值在第几个参数中?

答:CreateSemaphore共含有四个参数:

(ⅰ)lpSemaphoreAttributes SECURITY_ATTRIBUTES:定义了信号机的安全特性;

(ⅱ)lInitialCount Long:设置信号机的初始计数;

(ⅲ)lMaximumCount Long:设置信号机的最大计数;

(ⅳ)lpName String:指定信号机对象的名称。

       信号量的初值在第2个参数中。

③ 程序中P、V原语对应的实际Windows API函数是什么?写出这几条语句。

答:(ⅰ)WaitForSingleObject(FullSemaphore,INFINITE);       //P(full)

       (ⅱ)WaitForSingleObject(Mutex,INFINITE);                      //P(mutex)

       (ⅲ)ReleaseMutex(Mutex);                                                //V(mutex)

       (ⅳ)ReleaseSemaphore(EmptySemaphore,1,NULL);       //V(empty)

④ CreateMutex 能用CreateSemaphore替代吗?尝试修改程序,将信号量Mutex完全用CreateSemaphore及相关函数实现,并写出要修改的语句。

答:可以,因为互斥信号量本质上是二元信号量,因此只需要修改初始化代码和V操作代码即可,修改如下:

Mutex = CreateSemaphore(NULL, 1, 1, NULL);

ReleaseSemaphore(Mutex, 1, NULL);                                         //V(mutex)

  • 10
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有为肥宅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值