一、概述

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

二、实例

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

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

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

代码实现如下:


//生产者消费者问题,一个生产者,一个消费者,一个缓冲区。
#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. mfc 形式参数初始化

    初始化函数形参 int add(int a,int b) { return a+b; } add(,); ,,char*s=NULL) { printf("a=%d,b=%d,c=%d\n& ...

  2. python+soket实现UDP协议的客户/服务端中文聊天程序

    没什么特别的东西,网上烂大街的C/S框架.(基于windows 7 + python 3.4) 为了实现中文聊天,我加入了一点修改: msg.encode('utf-8') # msg 为输入(且将要 ...

  3. 一维码ITF 25简介及其解码实现(zxing-cpp)

    一维码ITF 25又称交插25条码,常用在序号,外箱编号等应用.交插25码是一种条和空都表示信息的条码,交插25码有两种单元宽度,每一个条码字符由五个单元组成,其中二个宽单元,三个窄单元.在一个交插2 ...

  4. C++ STL 学习笔记__(7)Set和multiset容器

    10.2.8 Set和multiset容器 set/multiset的简介 ²  set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列.元素插入过程是按排序规则插入,所以不能指 ...

  5. virtualBox linux centos 挂载u盘 解决乱码

    linux 挂载u盘 环境:虚拟机virtualBox5.2.8 linux centos6.5 64bit 第一步:插入u盘 第二步:virtualBox 设置启用u盘 提示:如果不知道哪个是你u盘 ...

  6. 关于Myeclipse的MyEclipse:Java was started but returned exit code=-1 错误

    我们在安装MyEclipse后有时会遇到这样一个问题,可以进入主界面软件也属于激活状态,但是过一会会报错, 并弹出MyEclipse:Java was started but returned exi ...

  7. JMeter的__threadGroupName使用注意事项

    JMeter从4.1版本开始引入了一个新函数"${__threadGroupName}",这个函数的作用是返回当前线程组的名字.${__threadGroupName}的用途也较为 ...

  8. C#多线程的几种使用

    参见链接   :http://www.jb51.net/article/46234.htm

  9. jenkins的时间与服务器的时间不一致

    解决办法:

  10. TensorFlow学习之路1-TensorFlow介绍

    TensorFlow是一个采用数据流图(data flow graphs),用于数据计算的开源软件库. 什么是数据流图? TensorFlow的数据流图是由“节点”(nodes)和“线”(edges) ...