一.实验要求
简单地说就是通过使用不同的API来实现两个不同版本的变量同步,生产者线程会使得counter值增加,消费者线程会使得counter值减少。它们都是共享counter这个变量。实现生产者-消费者问题的主要步骤是初始化缓冲,创建生产者、消费者进程,睡眠等待,终止应用程序。由于此次实验项目只关心counter变量的值,不在乎缓冲区里面的数据,故在这里就省去了申请缓冲区以及每一次都给缓冲区赋值的步骤。
二.实现步骤
当一个线程执行了EnterCritialSection之后,cs里面的信息便被修改了,以指明哪一个线程占用了它。而此时,并没有任何资源被“锁定”。不管什么资源,其它线程都还是可以访问的。只不过,在这个线程尚未执行LeaveCriticalSection之前,其它线程碰到EnterCritialSection语句的话,就会处于等待状态,相当于线程被挂起了。 这种情况下,就起到了保护共享资源的作用。
在主函数里面使用CreateMutex()创建一个互斥体对象,创建进程希望立即拥有互斥体,则设参数为TRUE。但这里是生产者消费者线程需要,故代码如下:
一个互斥体同时只能由一个线程拥有。如果一个线程拥有了互斥体,在没有通过ReleaseMutex释放互斥体时,其他线程必须等待其释放,故在这里使用了WaitForSingleObject方法。
Main函数最后也要释放互斥体。“谁生产谁释放”。
本次实验的输入是生产者数量、消费者数量、缓冲区大小以及所需生产产品总数。通过一个全局变量have_produce来统计生产者已经生产的数量,如果其等于end_produce_num,则表明达到生产目标,于是终止程序。Main函数的大致实现如下:
1) 简陋开始界面以及生产者数量等变量的输入。
2) 初始化临界区/创建互斥体3) 初始化计数器,为所需句柄、数组申请空间。
4) 开始计时。
5) 创建生产者、消费者线程。
6) 等待线程结束,关闭句柄,停止计时。
7) 销毁关键段或释放互斥体。
三. 实验结果截图与性能比较
开始界面,欢迎来到同步世界。
输入1,选择临界区版本。根据提示输入如下:
即单消费者、单生产者、需要生产30个产品、缓冲区大小为4。
运行过程如下:
很明显可以看出,一个生产者(消费者)线程进入临界区之后就会连续进入临界区,连续生产(消费)产品,直至达到缓冲区大小上限(下限)。
所需时间为113毫秒。
此时输入Y以继续测试,输入N以退出程序。由于我们要继续测试,故输入Y。
以上是只有单消费者和单生产者的情况。现在来观察多消费者与多生产者的情况。
多生产者与多消费者同样也符合上述“一个生产者(消费者)线程进入临界区之后就会连续进入临界区,连续生产(消费)产品,直至达到缓冲区大小上限(下限)。”
以上都是生产者数量小于缓冲区大小的情况,现在来观察缓冲区大小小于生产者数量的情况。
从以上可以看出,虽然生产者数量大于缓冲区大小,但是每一个生产者进入临界区后还是只会生产不超过缓冲区大小的数量。
开始界面输入2,选择Mutex版本。与临界区版本一样,先讨论单消费者单生产者的情况,输入如下:
运行过程如下:
可以看到生产者与消费者交替抢占到mutex资源,counter在0和1之间交替出现。暂时还看不出来其他的特点。故观察多生产者多消费者的情况。
为了做更好的比较,输入与临界区一样的数据:3个生产者、3个消费者、30个产品总数、缓冲区大小为5。
从以上两图可以很明显地看出特点:当生产者(消费者)抢占到mutex资源的时候,其他生产者(消费者)也会接连抢占到资源,然后接连生产(消费)。这与临界区版本的一个很大的区别就是——同样是接连生产(消费),临界区版本的是只有一个生产者(消费者),而Mutex版本的是所有的生产者