C++ 模拟生产者与消费者模式等

设计并实现一个进程,该进程拥有一个生产者线程和一个消费者线程,它们使用N个不同的缓冲区(N为一个确定的数值,例如N=32)。需要使用如下信号量:
一个互斥信号量,用以阻止生产者线程和消费者线程同时操作缓冲区列表;
一个信号量,当生产者线程生产出一个物品时可以用它向消费者线程发出信号;
一个信号量,消费者线程释放出一个空缓冲区时可以用它向生产者线程发出信号。

主要程序结构

#include<iostream>
#include<Windows.h>
#include<process.h>
#include<vector>
using namespace std;

// 等价于WINAPI,约定使用stdcall函数调用,既被调用者负责清栈
#define STD __stdcall
// 确定缓冲区大小,这里选取5,便于展示和说明结果
#define LENGTH 5
// 随机时间
#define GETMYRAND() (int)(((double)rand()/(double)RAND_MAX)*300)

// 使用临界区来同步线程
CRITICAL_SECTION _cr;
//空信号量
HANDLE emptySemaphore = NULL;
//满信号量
HANDLE fullSemaphore = NULL;
// 缓冲区Buffer
vector<int> buffer;

// 消费者线程
DWORD STD Consumer(void* lp) {
while(true) {
    //等待判断缓冲区满的信号量
    WaitForSingleObject(fullSemaphore,0xFFFFFFFF);
    //进入临界区,线程同步,功能同互斥量 
    EnterCriticalSection(&_cr);
    //消费者线程从缓冲区中取出消费一个资源
    buffer.pop_back();
    //打印当前缓冲区可用资源数
    cout << "消费者消费一个资源,当前可用资源数:" << buffer.size() << endl;
    //离开临界区
    LeaveCriticalSection(&_cr);
    //释放判断缓冲区空的信号量
    ReleaseSemaphore(emptySemaphore,1,NULL);
    //线程睡眠随机时间
    Sleep(GETMYRAND());
}
return 0;
}

// 生产者线程
DWORD STD Producer(void* lp) {
while(true){
    //等待判断缓冲区空的信号量
    WaitForSingleObject(emptySemaphore, 0xFFFFFFFF);
    //进入临界区,线程同步,功能同互斥量 
    EnterCriticalSection(&_cr);
    //生产者线程向缓冲区中生成一个资源
    buffer.push_back(1);
    //打印当前缓冲区可用资源数
    cout << "生产者新生产一个资源,当前可用资源数:" << buffer.size() << endl;
    //离开临界区
    LeaveCriticalSection(&_cr);
    //释放判断缓冲区满的信号量
    ReleaseSemaphore(fullSemaphore, 1, NULL);
    //线程睡眠随机时间
    Sleep(GETMYRAND());
}
    return 0;
}

int main() {

//创建信号量
emptySemaphore = CreateSemaphore(NULL, LENGTH, LENGTH, NULL);
fullSemaphore = CreateSemaphore(NULL, 0, LENGTH, NULL);

//初始化临界区
InitializeCriticalSection(&_cr);
//开启多线程
HANDLE handles[2];
handles[1] = CreateThread(0, 0, &Producer, 0, 0, 0);
handles[0] = CreateThread(0, 0, &Consumer,  0, 0, 0);

//等待子线程执行完毕
WaitForMultipleObjects(2, handles, true, INFINITE); //"Join" trreads
//释放子线程
CloseHandle(handles[0]);
CloseHandle(handles[1]);
//释放临界区
DeleteCriticalSection(&_cr);
return 0;
}
一、生产者消费者线程互斥

使用critical area(临界区)来保证生产者和消费者的线程彼此互斥:


图片.png

在多线程开启前后分别初始化临界区和销毁临界区。
接下来,在生产者线程函数和消费者线程函数对缓冲区进行操作的代码前后启用临界区和离开临界区,来做到两个线程间互斥同步。

图片.png

图片.png

最终效果是,虽然两个线程都通过死循环不断执行循环体中代码,但是在其中一个线程执行临界区代码时,另一个线程被互斥阻塞。


图片.png
二、生产者、消费者分别在“满”和“空”时阻塞

生产者线程得到emptySemaphore信号量时,其计数器-1,开始生产资源。在生产结束后,使fullSemaphore的计数器+1。
消费者线程得到fullSemaphore信号量时,其计数器-1,开始生产资源。在生产结束后,使emptySemaphore的计数器+1。
通过上面所述过程,使用emptySemaphore和fullSemaphore两个信号量可以做到资源生产满时生产者阻塞,资源消费完时消费者阻塞的功能。


图片.png

图片.png

为了演示消费者阻塞效果,我们把生产者的生产效率降低,既在生产者线程结束后,Sleep睡眠的时间延长。


图片.png
查看效果:
图片.png

每隔大半秒出现一对生产者和消费者记录,说明资源不足时,已经阻塞消费者继续消费资源,而等待生产者Sleep休眠时间之后,生产出新的资源,消费者才继续消费。
为了演示生产者阻塞效果,我们把消费者的消费效率降低,既在消费者线程结束后,Sleep睡眠的时间延长。


图片.png
查看效果:
图片.png

当资源数到达5时,资源已满时,既阻塞生产者继续生产资源,而等待消费者Sleep休眠时间之后,消费掉资源,生产者才继续生产。

查阅资料及笔记:

在生产者和消费者的代码中经常出现DWORD WINAPI
其中,WINAPI是宏定义 #define WINAPI __stdcall
stdcall是一种函数调用约定,其清栈工作由被调用者执行,对于节省内存效果很好,但缺点是参数不是可变长类型。
另外还有__cdcel、__fastcall等。
临界区、互斥量、信号量、事件总结:
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量 。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
临界区的缺点是:没有办法知道进入临界区中的那个线程是生是死。如果那个线程在进入临界区后当掉了,而且没有退出来,那么系统就没有办法消除掉此临界区。
2. 互斥量(Mutex),信号量(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和 线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。
mutex和samephore都是信号量,但前者实现critical area的功能,后者带计数器。mutex可以说是计数为1的samephore。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容