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月23日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 27-ATM+购物车程序
1.需求 本章作业: 模拟实现一个ATM + 购物商城程序 额度 15000或自定义 实现购物商城,买东西加入 购物车,调用信用卡接口结账 可以提现,手续费5% 支持多账户登录 支持账户间转账 记录每 ...
- Kubernetes学习之路(十)之资源清单定义
一.Kubernetes常用资源 以下列举的内容都是 kubernetes 中的 Object,这些对象都可以在 yaml 文件中作为一种 API 类型来配置. 类别 名称 工作负载型资源对象 Pod ...
- webstorm 使用svn
webstorm 支持bootstrap 的class支持,比如 .text-center. 第一步:首先下载安装SVN服务器VisualSVN:附下载链接 https://sliksvn.com/d ...
- CodeForces 915D Almost Acyclic Graph
Description You are given a directed graph consisting of \(n\) vertices and \(m\) edges (each edge i ...
- PageIOLatch和PageLatch
Latch是轻量级的锁,它是SQL Server内部用来同步资源访问的一个数据结构,使数据的访问同步有序,这意味着,当一个线程获得资源R的Latch的独占使用权时,如果其他的线程也想访问这个Latch ...
- IT程序员每天的困扰:这TM为啥不可以?这TM也行?
如果有对 Python 感兴趣的程序员,可以加我们小助手的QQ:979950755 会免费送 Python 的视频教程噢! 随着IT互联网对社会的影响越来越重要,关乎人类的未来发展进程.所以现在很多媒 ...
- 我用 Python 爬取微信好友,最后发现一个大秘密
前言 你身处的环境是什么样,你就会成为什么样的人.现在人们日常生活基本上离不开微信,但微信不单单是一个即时通讯软件,微信更像是虚拟的现实世界.你所处的朋友圈是怎么样,慢慢你的思想也会变的怎么样.最近在 ...
- ASP.NET Core 接触&介绍
几年前从朋友口中了解到了微软出来一个ASP.NET Core ,当时还是1.0版本,聊天时还吐槽不好用之类的.前不久了解.NET Core 已经出3.0版本了,突然想试试,了解了解.ASP.NET C ...
- 前端常见算法面试题之 - 从尾到头打印链表[JavaScript解法]
题目描述 输入一个链表的头结点,从尾到头反过来打印出每个结点的值 实现思路 前端工程师看到这个题目,直接想到的就是,写个while循环来遍历链表,在循环中把节点的值存储在数组中,最后在把数组倒序后,遍 ...