一、概述

生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。

二、实例

(一) 一个生产者,一个消费者,一个缓冲区。

要满足生产者与消费者关系,我们需要保证以下两点:

  • 第一.从缓冲区取出产品和向缓冲区投放产品必须是互斥进行的。可以用关键段和互斥量来完成。
  • 第二.生产者要等待缓冲区为空,这样才可以投放产品,消费者要等待缓冲区不为空,这样才可以取出产品进行消费。并且由于有二个等待过程,所以要用二个事件或信号量来控制。

代码实现如下:


//生产者消费者问题,一个生产者,一个消费者,一个缓冲区。
#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多线程(十) 生产者与消费者问题的更多相关文章

  1. JAVA之旅(十五)——多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止

    JAVA之旅(十五)--多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止 我们接着多线程讲 一.生产者和消费者 什么是生产者和消费者?我们 ...

  2. 母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列

    简介 多线程通信一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天我们以实际生活中母鸡下蛋案例用代码剖析下实现过程.母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个 ...

  3. java 22 - 19 多线程之生产者和消费者的代码优化

    在之前,是把生产者录入数据和消费者获取数据的所有代码都分别写在各自的类中. 这样不大好 这次把生产者和消费者部分关键代码都写入资源类中: package zl_Thread; public class ...

  4. java 22 - 16 多线程之生产者和消费者的问题

    生产者和消费者问题的描述图 通过上图,我们可以发现: 生产者和消费者使用的都是同一个资源(肉包子) 所以,当使用线程的时候,这两类的锁也是同一把锁(为了避免出现线程安全问题) 例子:学生信息的录入和获 ...

  5. Java:多线程之生产者与消费者

    要求:用两个线程模拟存票.售票过程.但要求每存入一张票,就售出一张票,售出后,再存入,直到售完为止. 用到的知识点:线程等待.唤醒.可能的线程中断异常 下面的方式一和方式二采用的是唤醒所有等待的线程, ...

  6. 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型

    关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...

  7. Python 之并发编程之进程下(事件(Event())、队列(Queue)、生产者与消费者模型、JoinableQueue)

    八:事件(Event()) # 阻塞事件:    e = Event() 生成事件对象e    e.wait() 动态给程序加阻塞,程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值 ...

  8. 生产者与消费者以及ActiveMQ

    生产者与消费者以及ActiveMQ 一. 多线程实现生产者与消费者 1.1 生产者与消费者头文件 #pragma once #include <iostream> #include < ...

  9. JAVA基础再回首(二十五)——Lock锁的使用、死锁问题、多线程生产者和消费者、线程池、匿名内部类使用多线程、定时器、面试题

    JAVA基础再回首(二十五)--Lock锁的使用.死锁问题.多线程生产者和消费者.线程池.匿名内部类使用多线程.定时器.面试题 版权声明:转载必须注明本文转自程序猿杜鹏程的博客:http://blog ...

随机推荐

  1. BZOJ2439【中山市选2011】序列

    题面 题解 设$f[i]$表示将$[1,i]$修改为递增的最小代价, $g[i]$表示将$[i,n]$修改为递减的最小代价. $L[i]$表示将$[1,i]$修改为倒$\text V$的代价 $$ \ ...

  2. 监听Google Player下载并获取包名等信息

    一.解决思路 通过监听ContentObserver监听下载路径content://downloads 二.具体步骤 1 设置监听器 context.getContentResolver() .reg ...

  3. nodejs学习笔记(3)

    在看了Node8的特性后的总结,后续慢慢补充. 主要是针对一些编码规范,Node的v8引擎由于不同于其他语言的解析策略,因此需要注意一些特殊的方面. 1.try/catch使用对代码运行性能影响明显, ...

  4. Appium安卓与环境配置

    下载与安装: Appium-desktop项目地址:https://github.com/appium/appium-desktop 下载地址:https://github.com/appium/ap ...

  5. php-7.1.11-64位

    php-7.1.11-Win32-VC14-x64.zip 链接:https://pan.baidu.com/s/1w8-fJo8-oWrriHyWpU5Fpg 提取码:bd0e 复制这段内容后打开百 ...

  6. String字符串的方法

    String字符串在Java开发中是我们常用的一种数据类型,同时String字符串也为我们提供了大量的方法.通过一些实例的练习,我们可以对String字符串的方法有一个比较清楚的了解. 有一个字符串S ...

  7. XSS构造技巧

    利用字符编码: 百度曾经出过一个XSS漏洞,在一个<script>标签中输出一个变量,其中转义了双引号: var redirectUrl="\";alert(/XSS/ ...

  8. java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.ObjectMapper

    RabbitMq配置时常见错误 java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.ObjectMapper <de ...

  9. 解读Python编程中的命名空间与作用域

    变量是拥有匹配对象的名字(标识符).命名空间是一个包含了变量名称们(键)和它们各自相应的对象们(值)的字典.一个Python表达式可以访问局部命名空间和全局命名空间里的变量.如果一个局部变量和一个全局 ...

  10. Daily Scrum 11.10

    今日完成任务: 1.加入更改头像功能 2.解决不发送激活邮件和重置密码邮件的问题 3.在服务器上部署网站 4.加入匿名提问功能 明日任务: 黎柱金 修改数据库用户表,实现用户积分管理功能 晏旭瑞 解决 ...