多线程的Producer/Consumer模式

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。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值