多线程的Producer/Consumer模式

原创 2014年08月11日 17:46:34

Win32下多线程实现数据协同主要靠下面四种Synchronization Object:

  • event
  • mutex or critical section
  • semaphore
  • waitable timer
它们的同步主要靠WaitForSingleObject()这类的Wait Function。


生产者/消费者是多线程同步里的经典模式。保证读写某个对象时不冲突我们可以用Mutex或CriticalSection,那么如何保证生产者和消费者的操作的先后顺序呢?经典的有信号量,我们这里采用Event。

完整代码如下:

#undef UNICODE
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <string>

#define WITH_SYNCHRONIZATION

#define MSG_BUF_SIZE 128

class Message
{
private:
	char messageText[MSG_BUF_SIZE];
	bool bAlive;
#ifdef WITH_SYNCHRONIZATION
	HANDLE m_hMutex;
	HANDLE m_hEvent;
#endif

public:
	Message()
	{
		memset(messageText, 0, sizeof(messageText));
		bAlive = true;
	
#ifdef WITH_SYNCHRONIZATION
		m_hMutex = (HANDLE)CreateMutex(NULL, false, "MessageMutex");
		if (m_hMutex == NULL)
		{
			printf("Create Mutex failed. \n");
		}
		m_hEvent = CreateEvent(NULL, false, false, "MessageEvent");
		if (m_hEvent == NULL)
		{
			printf("Create Event failed. \n");
		}
#endif
	}

	~Message()
	{
#ifdef WITH_SYNCHRONIZATION
		CloseHandle(m_hMutex);
		CloseHandle(m_hEvent);
#endif
	}

	void SetMessage(char *msg)
	{
#ifdef WITH_SYNCHRONIZATION
		DWORD dwWaitResult = WaitForSingleObject(m_hMutex, INFINITE);
		if (dwWaitResult != WAIT_OBJECT_0)
		{
			printf("WaitForSingleObject Failed in SetMessage(). \n");
		}
		printf("Acquired Mutex in SetMessage(). \n");
#endif

	//	printf("SetMessage. \n");
		char* szTmp = &messageText[0];
		while (*msg != 0)
		{
			*(szTmp++) = *msg++;
			Sleep(5);
		}
		*szTmp = 0;

#ifdef WITH_SYNCHRONIZATION
		printf("SetEvent in SetMessage(). \n");
		if ( !SetEvent(m_hEvent) )
		{
			printf("SetEvent() failed in SetMessage(). \n");
		}
		printf("Release Mutex in SetMessage(). \n");
		if (!ReleaseMutex(m_hMutex))
		{
			printf("Release Mutex Failed in SetMessage(). \n");
		}
#endif
	}

	void ProcessMessages()
	{
		while (bAlive)
		{
#ifdef WITH_SYNCHRONIZATION
			DWORD dwWaitResult = WaitForSingleObject(m_hEvent, 1500);
			if (dwWaitResult == WAIT_TIMEOUT || bAlive != true)
			{
				printf("TimeOut or Message Die. \n");
				break;
			}
			else if (dwWaitResult == WAIT_ABANDONED)
			{
				printf("WaitForSingleObject(Event) failed in ProcessMessage(). \n");
				return;
			}
			else if (dwWaitResult == WAIT_OBJECT_0)
			{
				printf("Saw Event in ProcessMessages(). \n");
			}

			dwWaitResult = WaitForSingleObject(m_hMutex, INFINITE);
			if (dwWaitResult != WAIT_OBJECT_0)
			{
				printf("WaitForSingleObject(Mutex) failed in ProcessMessages(). \n");
			}
			printf("Acquired Mutex in ProcessMessages. \n");
#endif

		//	printf("ShowMessage. \n");
			if (strlen(messageText) != 0)
			{
				printf("%s \n", messageText);
			}
			messageText[0] = 0;

#ifdef WITH_SYNCHRONIZATION
			printf("Release Mutex in ProcessMessages(). \n");
			if (!ReleaseMutex(m_hMutex))
			{
				printf("Release Mutex Failed in ProcessMessages(). \n");
			}
#endif
		}
	}

	void Die()
	{
		bAlive = false;
	}

};


class Producer
{
private:
	Message* msg;

public:
	Producer(Message* m) : msg(m) { }

	static unsigned __stdcall ProducerEntryPoint(void* pThis)
	{
		Producer* pTh = (Producer*)pThis;
		pTh->StartUp();

		return 1;
	}

	void StartUp()
	{
		char szTmp[MSG_BUF_SIZE];
		for (int i = 1; i < 8; ++i)
		{
			sprintf(szTmp, "%d%d%d%d%d%d%d%d%d%d%d%d", i, i, i, i, i, i, i, i, i, i, i, i);
			msg->SetMessage(szTmp);
			Sleep(1000);
		}

		printf("Producer finished her job. Gone. \n");
	}
};

class Consumer
{
private:
	Message* msg;

public:
	Consumer(Message* m) : msg(m) { }

	static unsigned __stdcall ConsumerEntryPoint(void* pThis)
	{
		Consumer* pTh = (Consumer*)pThis;
		pTh->StartUp();

		return 1;
	}

	void StartUp()
	{
		msg->ProcessMessages();
	}

	void Die()
	{
		msg->Die();
	}
};


int main()
{
	Message* msg = new Message();

	Producer* producer = new Producer(msg);
	HANDLE hThread1;
	unsigned thread1Id;
	hThread1 = (HANDLE)_beginthreadex(NULL,
		0,
		Producer::ProducerEntryPoint,
		producer,
		CREATE_SUSPENDED,
		&thread1Id);
	if (hThread1 == NULL)
	{
		//std::cout << "Failed to create Thread 1." << std::endl;
		printf("Failed to create Thread 1. \n");
	}
	DWORD dwExitCode;
	GetExitCodeThread(hThread1, &dwExitCode);
	printf("initial thread 1 exit code = %d \n", dwExitCode);
	//std::cout << "initial thread 1 exit code = " << dwExitCode << std::endl;

	Consumer* consumer = new Consumer(msg);
	HANDLE hThread2;
	unsigned thread2Id;
	hThread2 = (HANDLE)_beginthreadex(NULL,
		0,
		Consumer::ConsumerEntryPoint,
		consumer,
		CREATE_SUSPENDED,
		&thread2Id);
	if (hThread2 == NULL)
	{
		printf("Failed to create Thread 1. \n");
		//std::cout << "Failed to create Thread 2." << std::endl;
	}
	GetExitCodeThread(hThread1, &dwExitCode);
	printf("initial thread 2 exit code = %d \n", dwExitCode);
	//std::cout << "initial thread 2 exit code = " << dwExitCode << std::endl;

	ResumeThread(hThread1);
	ResumeThread(hThread2);

	WaitForSingleObject(hThread1, INFINITE);  //producer
	consumer->Die();
	WaitForSingleObject(hThread2, INFINITE);  //consumer

	GetExitCodeThread(hThread1, &dwExitCode);
	printf("thread 1 exit with code : %d \n", dwExitCode);
	//std::cout << "thread 1 exit with code : " << dwExitCode << std::endl;

	GetExitCodeThread(hThread2, &dwExitCode);
	printf("thread 2 exit with code : %d \n", dwExitCode);
	//std::cout << "thread 2 exit with code : " << dwExitCode << std::endl;

	CloseHandle(hThread1);
	CloseHandle(hThread2);


	delete(producer);
	producer = NULL;
	delete(consumer);
	consumer = NULL;
	delete(msg);
	msg = NULL;

	system("pause");
	return 0;
}


几个比较Tricky的地方:

1.  ProcessMessages()里,

DWORD dwWaitResult = WaitForSingleObject(m_hEvent, 1500);

这里的限定了超时时间为1500ms,下断点调试时,可能会导致超时。调试时可将数字改大一些,或者设置为INFINITE。那么退出条件要加倍注意。

2. 注释和解注释#define WITH_SYNCHRONIZATION可以看是否处理多线程同步的区别。 为了保证注释掉WITH_SYNCHRONIZATION时程序能正常退出(不会陷入死循环),ProcessMessages()里的循环加入了一个bool类型的控制变量。且bAlive的改变时机很重要,在输出完所有有效信息后,将bAlive设置为false退出循环。

WaitForSingleObject(hThread1, INFINITE);  //producer
consumer->Die();
WaitForSingleObject(hThread2, INFINITE);  //consumer

3. 用Event来控制Producer和Consumer操作的先后顺序。

m_hEvent = CreateEvent(NULL, false, false, "MessageEvent");  m_hEvent 的初始无信号;且m_hEvent被等待线程释放后,自动恢复无信号状态。

CreateEvent
函数功能描述:创建或打开一个命名的或无名的事件对象
函数原型:
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,   // 安全属性
  BOOL bManualReset,   // 复位方式
  BOOL bInitialState,   // 初始状态
  LPCTSTR lpName   // 对象名称
);
参数:
lpEventAttributes:
      [输入]一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
      Windows NT/2000:lpEventAttributes的结构中的成员为新的事件指定了一个安全符。如果lpEventAttributes是NULL,事件将获得一个默认的安全符。
bManualReset:
      [输入]指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当事件被一个等待线程释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState:
      [输入]指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName:
      [输入]指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。

SetMessage()里,if ( !SetEvent(m_hEvent) )... 给m_hEvent设置信号。

ProcessMessages()里,DWORD dwWaitResult = WaitForSingleObject(m_hEvent, 1500);  需要等待m_hEvent。




Java多线程:Producer-Consumer不同的几种实现方式

生产者消费者问题是一个典型的线程同步问题。 主要有如下实现方式: wait() notifyAll() class Queue { //共享队列的目的用于...
  • langjian2012
  • langjian2012
  • 2015-03-22 20:55:42
  • 1379

多线程设计模式之——Producer-Consumer Pattern

此模式是在生产者与消费者之间加入一个“桥梁参与者”来缓冲线程之间的处理速度差。一般可以存在多个生产者与消费者,但当双方都只有一个的时候,又称为Pipe Pattern。 例子:假设有2个生产者,2个消...
  • dyllove98
  • dyllove98
  • 2013-02-28 12:36:19
  • 1040

多线程设计模式——Producer-Consumer生产者消费者模式

Producer-Consumer生产者消费者模式
  • buyoufa
  • buyoufa
  • 2016-07-08 21:59:49
  • 1845

RabbitMQ之Consumer消费模式(Push & Pull)

概述 消息中间件有很多种,进程也会拿几个来对比对比,其中一种对比项就是消费模式。消息的消费模式分Push,Push两种,或者两者兼具。RabbitMQ的消费模式就是兼具Push和Pull。 本文通...
  • u013256816
  • u013256816
  • 2017-03-17 16:12:47
  • 7343

Producer/Consumer 多线程处理文件

  • 2014年03月21日 10:28
  • 17.09MB
  • 下载

C# Producer Consumer (生产者消费者模式)demo

刚接触C#不久,想做个C,C++ Server中经常使用的Producer Consumer模式,以满足多任务排队多线程处理的模式。于是写了下面的demo。      第一套代码将producer ...
  • irwin_chen
  • irwin_chen
  • 2012-04-23 22:17:24
  • 10589

Producer-Consumer模型:一、简介

Producer-Consumer模型是一种为了加快系统响应数据的异步模型。1.它试图解决什么问题?系统中一些慢操作(例如网络IO,数据统计等等)会阻塞主进程的运行,从而使得系统的吞吐量大大降低。如果...
  • billll
  • billll
  • 2016-10-23 11:28:44
  • 766

kafka 0.10.0.0 版本

kafka 0.10.0.0 版本一、安装kafka1)下载路径:http://apache.fayea.com/kafka/0.10.0.0/kafka_2.11-0.10.0.0.tgz 2)解压...
  • zhengyong15984285623
  • zhengyong15984285623
  • 2016-07-28 13:56:02
  • 12755

onvif两种notify方式解读-------Basic Notification

Basic Notification  interface  以下逻辑实体参与此通知模式: Client-------实现 consumer通知接口 Event service-...
  • no_pao_spite
  • no_pao_spite
  • 2017-05-31 15:50:42
  • 858

java多线程之Producer-Consumer模式

一、Producer-Consumer设计模式Producer是生产者的意思,指的是生产数据的线程,Consumer则是消费者的意思,指的是使用数据的线程。生产者安全的将数据交给消费者,虽然仅是这样看...
  • smartdt
  • smartdt
  • 2018-02-01 09:22:02
  • 152
收藏助手
不良信息举报
您举报文章:多线程的Producer/Consumer模式
举报原因:
原因补充:

(最多只允许输入30个字)