Win32下多线程实现数据协同主要靠下面四种Synchronization Object:
- event
- mutex or critical section
- semaphore
- waitable timer
生产者/消费者是多线程同步里的经典模式。保证读写某个对象时不冲突我们可以用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。