windows多线程(十) 生产者与消费者问题
一、概述
生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
二、实例
(一) 一个生产者,一个消费者,一个缓冲区。
要满足生产者与消费者关系,我们需要保证以下两点:
- 第一.从缓冲区取出产品和向缓冲区投放产品必须是互斥进行的。可以用关键段和互斥量来完成。
- 第二.生产者要等待缓冲区为空,这样才可以投放产品,消费者要等待缓冲区不为空,这样才可以取出产品进行消费。并且由于有二个等待过程,所以要用二个事件或信号量来控制。
代码实现如下:
//生产者消费者问题,一个生产者,一个消费者,一个缓冲区。
#include <iostream>
#include <windows.h>
using namespace std;
DWORD WINAPI ProducerThread(LPVOID);
DWORD WINAPI ConsumerThread(LPVOID);
const int PRODUCT_NUM = 10; //总共生产10个产品
int g_Buffer = 0; //缓冲区
CRITICAL_SECTION g_csVar; //互斥锁
HANDLE g_hEventBufEmpty, g_hEventBufFull;
int main()
{
InitializeCriticalSection(&g_csVar);
g_hEventBufEmpty = CreateEvent(NULL, false, true, NULL); //缓冲区为空事件
g_hEventBufFull = CreateEvent(NULL, false, false, NULL); //缓冲区满事件
const int THREAD_NUM = 2;
HANDLE handle[THREAD_NUM];
handle[0] = CreateThread(NULL, 0, ProducerThread, NULL, 0, NULL); //生产者线程
handle[1] = CreateThread(NULL, 0, ConsumerThread, NULL, 0, NULL); //消费者线程
WaitForMultipleObjects(THREAD_NUM, handle, true, INFINITE);
DeleteCriticalSection(&g_csVar);
CloseHandle(handle[0]);
CloseHandle(handle[1]);
CloseHandle(g_hEventBufEmpty);
CloseHandle(g_hEventBufFull);
return 0;
}
DWORD WINAPI ProducerThread(LPVOID p)
{
for (int i = 1; i <= PRODUCT_NUM; i++)
{
WaitForSingleObject(g_hEventBufEmpty, INFINITE); //等待缓冲区为空
EnterCriticalSection(&g_csVar);
g_Buffer = i;
cout << "生产者将数据 " << g_Buffer << " 放入缓冲区!" << endl;
LeaveCriticalSection(&g_csVar);
SetEvent(g_hEventBufFull); //触发事件,缓冲区满
}
return 0;
}
DWORD WINAPI ConsumerThread(LPVOID p)
{
for (int i = 1; i <= PRODUCT_NUM; i++)
{
WaitForSingleObject(g_hEventBufFull, INFINITE); //等待缓冲区满
EnterCriticalSection(&g_csVar);
cout << "\t\t\t\t消费者将数据 " << g_Buffer << " 从缓冲区取出!" << endl;
LeaveCriticalSection(&g_csVar);
SetEvent(g_hEventBufEmpty); //触发事件,清空缓冲区
}
return 0;
}
运行结果如下,生产者等待缓冲区为空的时候才向缓冲区投放产品,消费者等待缓冲区满的时候才取走产品。

(二) 一个生产者,两个消费者,一个缓冲池(四个缓冲区)
相比于一个生产者,一个消费者,一个缓冲区,生产者由一个变成多个不难处理,多开线程就可以,需要注意的是缓冲区的变化,可以利用两个信号量就可以解决这种缓冲池有多个缓冲区的情况。用一个信号量A来记录为空的缓冲区个数,另一个信号量B记录非空的缓冲区个数,然后生产者等待信号量A,消费者等待信号量B就可以了。
代码实现如下:
// 一个生产者,两个消费者,一个缓冲池(四个缓冲区)
#include <iostream>
#include <windows.h>
using namespace std;
DWORD WINAPI ProducerThread(LPVOID);
DWORD WINAPI ConsumerThread(LPVOID); // 两个消费者,开两个线程就行了
const int PRODUCT_NUM = 16; //产品总数
const int BUFFER_SIZE = 4; //缓冲区大小
int g_Buffer[BUFFER_SIZE];
CRITICAL_SECTION g_csVar; // 互斥锁
HANDLE g_hEventBufEmpty, g_hEventBufFull;
int g_i = 0, g_j = 0;
int main()
{
InitializeCriticalSection(&g_csVar);
g_hEventBufEmpty = CreateSemaphore(NULL, 4, 4, NULL); //记录空缓冲区个数信号量
g_hEventBufFull = CreateSemaphore(NULL, 0, 4, NULL); //记录满缓冲区个数信号量
const int THREAD_NUM = 3; //线程数
HANDLE handle[THREAD_NUM];
memset(g_Buffer, 0, sizeof(g_Buffer)); //缓冲池清零
handle[0] = CreateThread(NULL, 0, ProducerThread, NULL, 0, NULL); //生产者线程
handle[1] = CreateThread(NULL, 0, ConsumerThread, NULL, 0, NULL); //消费者线程1
handle[2] = CreateThread(NULL, 0, ConsumerThread, NULL, 0, NULL); //消费者线程2
WaitForMultipleObjects(THREAD_NUM, handle, true, INFINITE);
for (int i = 0; i<THREAD_NUM; i++)
{
CloseHandle(handle[i]);
}
CloseHandle(g_hEventBufEmpty);
CloseHandle(g_hEventBufFull);
DeleteCriticalSection(&g_csVar);
return 0;
}
DWORD WINAPI ProducerThread(LPVOID p)
{
for (int i = 1; i <= PRODUCT_NUM; i++)
{
WaitForSingleObject(g_hEventBufEmpty, INFINITE); //生产者等待空缓冲区
EnterCriticalSection(&g_csVar);
g_Buffer[g_i] = i;
cout << "生产者在第 " << g_i << " 个缓冲池中放入数据 " << g_Buffer[g_i] << endl;
g_i = (g_i + 1) % BUFFER_SIZE; //g_i自增,并实现在缓冲池中循环
LeaveCriticalSection(&g_csVar);
ReleaseSemaphore(g_hEventBufFull, 1, NULL); //生产完产品后,记录满缓冲区个数信号量加一,即记录现有产品数
}
cout << "生产者完成任务,线程结束运行!" << endl;
return 0;
}
DWORD WINAPI ConsumerThread(LPVOID p)
{
for (int i = 1; i <= PRODUCT_NUM; i++)
{
WaitForSingleObject(g_hEventBufFull, INFINITE); //消费者等待缓冲区有产品(不为空)
EnterCriticalSection(&g_csVar);
cout << "\t\t\t编号为 " << GetCurrentThreadId() << " 的消费者在第 " << g_j << " 个缓冲池中取走数据 " << g_Buffer[g_j] << endl;
if (g_Buffer[g_j] == PRODUCT_NUM) //最后一个产品已经被取走,此时需要退出消费者线程。
{
LeaveCriticalSection(&g_csVar);
ReleaseSemaphore(g_hEventBufFull, 1, NULL); //这里信号量加一,通知其它消费者有数据了(实际没有),使其它消费者执行这里的if语句,结束线程。
break;
}
g_j = (g_j + 1) % BUFFER_SIZE; //g_i自增,并实现在缓冲池中循环
LeaveCriticalSection(&g_csVar);
ReleaseSemaphore(g_hEventBufEmpty, 1, NULL);
}
cout << "编号为 " << GetCurrentThreadId() << " 的消费者结束运行! " << endl;
return 0;
}
运行结果如下所示:

参考资料:秒杀多线程第十篇 生产者消费者问题
windows多线程(十) 生产者与消费者问题的更多相关文章
- JAVA之旅(十五)——多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止
JAVA之旅(十五)--多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止 我们接着多线程讲 一.生产者和消费者 什么是生产者和消费者?我们 ...
- 母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列
简介 多线程通信一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天我们以实际生活中母鸡下蛋案例用代码剖析下实现过程.母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个 ...
- java 22 - 19 多线程之生产者和消费者的代码优化
在之前,是把生产者录入数据和消费者获取数据的所有代码都分别写在各自的类中. 这样不大好 这次把生产者和消费者部分关键代码都写入资源类中: package zl_Thread; public class ...
- java 22 - 16 多线程之生产者和消费者的问题
生产者和消费者问题的描述图 通过上图,我们可以发现: 生产者和消费者使用的都是同一个资源(肉包子) 所以,当使用线程的时候,这两类的锁也是同一把锁(为了避免出现线程安全问题) 例子:学生信息的录入和获 ...
- Java:多线程之生产者与消费者
要求:用两个线程模拟存票.售票过程.但要求每存入一张票,就售出一张票,售出后,再存入,直到售完为止. 用到的知识点:线程等待.唤醒.可能的线程中断异常 下面的方式一和方式二采用的是唤醒所有等待的线程, ...
- 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型
关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...
- Python 之并发编程之进程下(事件(Event())、队列(Queue)、生产者与消费者模型、JoinableQueue)
八:事件(Event()) # 阻塞事件: e = Event() 生成事件对象e e.wait() 动态给程序加阻塞,程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值 ...
- 生产者与消费者以及ActiveMQ
生产者与消费者以及ActiveMQ 一. 多线程实现生产者与消费者 1.1 生产者与消费者头文件 #pragma once #include <iostream> #include < ...
- JAVA基础再回首(二十五)——Lock锁的使用、死锁问题、多线程生产者和消费者、线程池、匿名内部类使用多线程、定时器、面试题
JAVA基础再回首(二十五)--Lock锁的使用.死锁问题.多线程生产者和消费者.线程池.匿名内部类使用多线程.定时器.面试题 版权声明:转载必须注明本文转自程序猿杜鹏程的博客:http://blog ...
随机推荐
- 北京Uber优步司机奖励政策(4月22日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 【python3】酷狗音乐及评论回复下载
新年快乐,上班第一天分享一个python源码,功能比较简单,就是实现酷狗音乐的音乐文件(包含付费音乐)和所有评论回复的下载. 以 米津玄師 - Lemon 为例, 以下为效果图: 1.根据关键词搜索指 ...
- 【CQOI2017】小Q的棋盘
题面 题解 根据题意,不回头是最好的(显然法) \(dfs\)找到最长链,设长度为\(\mathrm{L}\),然后分类讨论: 如果\(\mathrm{L} > m\),答案就是\(m + 1\ ...
- spark-submit python 程序,"/home/.python-eggs" permission denied 问题解决
问题描述,spark-submit 用 yarn 模式提交一个python 脚本运行程序,运行到需要分布式的部分,即map/mapPartition等等RDD的时候,或者actor RDD的时候,报错 ...
- java面试资源(面试题、面试经验等)
两年JAVA程序员的面试总结 https://www.cnblogs.com/xuwujing/p/7613084.html 2018JAVA面试题附答案(长期更新) https://blog.csd ...
- 有关onpropertychange事件
<div style="border:1px solid #fc0;height:24px;width:300px;" id="target">&l ...
- 关于matlab向文件写入数据的方法——留着备用
MATLAB数据采集的时候,往往需要把得到的数据保存下来. fid = fopen(文件名,‘打开方式’): 说明:fid用于存储文件句柄值,如果fid>0,这说明文件打开成功.打开方式有如下选 ...
- MySQL(MariaDB)基础之一:编译安装
一.cmake介绍 cmake的重要特性之一是其独立于源码的编译功能,即编译工作可以在另一个指定的目录中而非源码目录中进行,这可以保证源码目录不受任何一次编译影响,因此在同一个源码树上可以进行多次不同 ...
- Swing的概述
Swing在Java中是一个包,下面就是它的基本概述:简介: Java为了方便图形界面的实现, 专门设计了类库来满足各种各样的图形元素和用户交互事件, 该类库即为抽象窗口工具箱(Abstract Wi ...
- Netty源码分析第2章(NioEventLoop)---->第6节: 执行select操作
Netty源码分析第二章: NioEventLoop 第六节: 执行select操作 分析完了selector的创建和优化的过程, 这一小节分析select相关操作 跟到跟到select操作的入口 ...