一.实验要求
简单地说就是通过使用不同的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版本的是所有的生产者(消费者)都参与其中,并且参与先后顺序随机。
现在再来看看生产者数量大于缓冲区大小的情况。
综合两种情况,可以发现Mutex版本的特点如下:
当生产者(消费者)抢占到mutex资源的时候,其他生产者(消费者)也会接连抢占到资源,然后接连生产(消费),而且接连生产(消费)的上限为生产者数量与缓冲区大小的最小值,即min(producer_Count, bufferSize)。这又是与临界区的另一区别,临界区counter上限为bufferSize,它不考虑生产者的数量。现在比较两个版本的性能快慢。
单消费者单生产者,生产1000个产品,缓冲区大小>生产者数量,临界区版本相对会快了一点。
多消费者多生产者,生产3000个产品,缓冲区大小>生产者数量,临界区版本也快了一点。
多消费者多生产者,生产1000个产品,缓冲区大小<生产者数量,临界版本还是快了一点。
因此,初步得出结论:临界区版本性能会比Mutex版本好一点。
尝试分析原因:临界区版本中一旦一个生产者进入临界区,(一般来说)就能连续进入临界区,且能一直生产到缓冲区大小的数量。而Mutex版本需要在不同的生产者之间切换,且能连续生产的数量是生产者数量与缓冲区大小的最小值,这在一定程度上增加了时间消耗。
四.代码实现
#include <windows.h>
#include <process.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <ctime>
using namespace std;
int producerCt; // 生产者数量
int consumerCt; // 消费者数量
int end_produce_num; // 生产产品个数
int counter; // 当前counter值
int have_produce; // 已经生产个数
int bufferSize; // 缓冲区大小
HANDLE hMutex;
CRITICAL_SECTION critical_section; //关键段
// 临界区:生产者线程函数
DWORD WINAPI producer1(LPVOID Param) {
int index = *(int*)Param; // 获取线程对应的参数下标
while (have_produce < end_produce_num) { // 只有目前生产数目小于所需总数才能进入循环
EnterCriticalSection(&critical_section); // 进入临界区
if (counter < bufferSize && have_produce < end_produce_num) { // counter不能大于缓冲区大小
have_produce++; // 目前生产的产品总数加1
counter++; // 生产者生产一个产品,counter值加1
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY |
FOREGROUND_GREEN); // 设置输出颜色
cout << "No." << index
<< " Producer, Counter: " << counter
<< " , totol_produce: " << have_produce << endl;
}
LeaveCriticalSection(&critical_section); // 离开临界区
}
return 0;
}
// 临界区:消费者线程函数
DWORD WINAPI consumer1(LPVOID Param) {
int index = *(int*)Param;
while (have_produce < end_produce_num) {
EnterCriticalSection(&critical_section);
if (have_produce < end_produce_num && counter > 0) {
counter--;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY |
FOREGROUND_BLUE | FOREGROUND_RED);
cout << "No." << index
<< " Consumer, Counter: " << counter << endl;
}
LeaveCriticalSection(&critical_section);
}
return 0;
}
// 互斥锁: 生产者线程函数
DWORD WINAPI producer2(LPVOID Param) {
int index = *(int*)Param;
while (have_produce < end_produce_num) {
WaitForSingleObject(hMutex, INFINITE); // 无限等待其他线程释放mutex
if (have_produce < end_produce_num && counter < bufferSize) {
have_produce++;
counter++;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY |
FOREGROUND_GREEN);
cout << "No." << index
<< " Producer, Counter: " << counter
<< " , totol_produce: " << have_produce << endl;
}
ReleaseMutex(hMutex); // 释放mutex
}
return 0;
}
// 互斥锁: 消费者线程函数
DWORD WINAPI consumer2(LPVOID Param) {
int index = *(int*)Param;
while (have_produce < end_produce_num) {
WaitForSingleObject(hMutex, INFINITE);
if (have_produce < end_produce_num && counter > 0) {
counter--;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY |
FOREGROUND_BLUE | FOREGROUND_RED);
cout << "No." << index
<< " Consumer, Counter: " << counter << endl;
}
ReleaseMutex(hMutex);
}
return 0;
}
int main(int argc, char *argv[]) {
while (true) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY |
FOREGROUND_GREEN | FOREGROUND_RED);
cout << "\n\n\n\n\n ----------------------------------------------------"
<< "\n | Welcome to Synchronous World ^_^ |"
<< "\n | |"
<< "\n | Version1: CriticalSection Version2: Mutex |"
<< "\n ----------------------------------------------------"
<< "\n Please input 1 or 2 to choose the version:";
string num;
while (cin >> num) {
if (num == "1" || num == "2") break;
else cout << "Valid input.Please input 1 or 2:";
}
system("cls");
// 输入生产者数目
cout << "Please input the count of producer:";
while (cin >> producerCt) {
if (producerCt > 0) break;
else cout << "The count of producer should be positive.\nPlease input again: ";
}
// 输入消费者数目
cout << "Please input the count of consumer:";
while (cin >> consumerCt) {
if (consumerCt > 0) break;
else cout << "The count of consumer should be positive.\nPlease input again: ";
}
// 输入所需生产总数
cout << "Please input the number of product need to produce:";
while (cin >> end_produce_num) {
if (end_produce_num > 0) break;
else cout << "The number of product should be positive.\nPlease input again: ";
}
// 输入缓冲区大小
cout << "Please input the size of buffer:";
while (cin >> bufferSize) {
if (bufferSize > 0) break;
else cout << "The size of buffer should be positive.\nPlease input again: ";
}
system("cls"); // 清屏
if (num == "1") InitializeCriticalSection(&critical_section); // 初始化临界区
else hMutex = CreateMutex(NULL, FALSE, NULL); // 创建一个互斥体
// 初始化计数器
counter = 0;
have_produce = 0;
HANDLE* handles = new HANDLE[producerCt + consumerCt];
DWORD* ThreadId = new DWORD[producerCt + consumerCt];
// 数组储存的是线程下标参数
int* index_pro = new int[producerCt];
for (int i = 0; i < producerCt; i++) index_pro[i] = i; // 1, 2, 3, ... producerCt
int* index_con = new int[consumerCt];
for (int i = 0; i < consumerCt; i++) index_con[i] = i; // 1, 2, 3, ... consumerCt
clock_t start, finish;
start = clock(); // 开始计时
int threadCount = 0; // 线程数量
if (num == "1") { // 临界区
for (int i = 0; i < producerCt; ++i, ++threadCount) // 创建生产者线程
handles[threadCount] = CreateThread(NULL, 0, producer1, index_pro + i, 0, &ThreadId[threadCount]);
for (int i = 0; i < consumerCt; ++i, ++threadCount) // 创建消费者线程
handles[threadCount] = CreateThread(NULL, 0, consumer1, index_con + i, 0, &ThreadId[threadCount]);
} else { //互斥锁
for (int i = 0; i < producerCt; ++i, ++threadCount) // 创建生产者线程
handles[threadCount] = CreateThread(NULL, 0, producer2, index_pro + i, 0, &ThreadId[threadCount]);
for (int i = 0; i < consumerCt; ++i, ++threadCount) // 创建消费者线程
handles[threadCount] = CreateThread(NULL, 0, consumer2, index_con + i, 0, &ThreadId[threadCount]);
}
WaitForMultipleObjects(threadCount, handles, TRUE, INFINITE); // 等待线程结束
for (int i = 0; i < threadCount; i++) { // 关闭句柄
CloseHandle(handles[i]);
}
finish = clock(); // 停止计时
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY |
FOREGROUND_GREEN | FOREGROUND_RED);
cout << "-----------------------------------------------"
<< "\n| The task of producing is done. |"
<< "\n-----------------------------------------------"
<< "\n Version: " << ((num == "1") ? "CriticalSection" : "Mutex")
<< "\n Producer Count: " << producerCt
<< "\n Consumer Count: " << consumerCt
<< "\n Buffer Size: " << bufferSize
<< "\n Total production number: " << end_produce_num
<< "\n Total spending time: " << finish - start << "/" << CLOCKS_PER_SEC << "s"
<< "\n Continue / End ( Y / N): ";
if (num == "1") DeleteCriticalSection(&critical_section); // 销毁关键段
else ReleaseMutex(hMutex); //释放互斥体
string ans;
while (cin >> ans) {
if (ans == "Y" || ans == "N") break;
else cout << "Valid input.Please input Y or N:";
}
if (ans == "N") break; // 退出程序
system("cls");
}
system("pause");
return 0;
}
五. 思考与总结
Critical Section:counter最小值为0,最大值为缓冲区大小;
Mutex:counter最小值为0,最大值为min(生产者数量,缓冲区大小)。虽然有时候遇到问题会感到些许的迷茫,但是经过自己在仔细查阅前后代码或者在搜索引擎上的一番努力之后找到问题解决的方法时,就会感觉很有成就感。这种感觉是直接问别人所不能体会到的。我想每一次实验都是在锻炼我们发现问题并解决问题的能力。
有待改善的地方还很多,努力前进的脚步不能停下来。
六. 参考网站
http://blog.csdn.net/greston/article/details/8162984
CRITICAL_SECTION详解
http://a547705232.blog.163.com/blog/static/1724915952013625101330729/
CreateMutex, ReleaseMutex, OpenMutex
http://www.cnblogs.com/wind-net/archive/2012/08/29/2661702.html
WINDOWS API ——CREATEMUTEX——创建互斥对象