在Windows平台上开发多线程应用程序时,经常需要使用Win32 API中的CreateThread()函数来创建线程。然而,直接使用这些底层API可能会引入一些难以预料的问题,比如线程同步启动的问题、消息队列的初始化问题等。为了解决这些问题,可以将这些行为封装到一个类中,以确保线程的正确行为并防止运行时错误。
在多线程系统中,有时需要同步启动所有线程。如果一个工作线程在其他线程有机会初始化之前就开始处理,可能会导致问题。因此,首先创建所有线程,然后使用同步事件同时启动它们,可以解决这个问题。
Win32线程API也有一些特殊性,如果管理不当,可能会导致运行时的间歇性失败。其中一个问题围绕着消息队列及其创建时机。在调用CreateThread()之后,消息队列不会立即初始化。新线程需要首先运行。向没有消息队列的线程发送PostThreadMessage()会导致函数失败。
因此,在设计Win32应用程序时,使用一个封装类来强制正确的行为并防止运行时错误是有益的。虽然存在许多Win32线程的包装类实现,但发现没有一个解决了上述问题。这里提供的ThreadWin类具有以下优点:
ThreadWin提供了Win32线程的封装。构造函数允许命名线程并控制是否需要同步启动。
class ThreadWin {
public:
ThreadWin(const CHAR* threadName, BOOL syncStart = TRUE);
// ...
};
从这个类继承并实现纯虚函数Process()。
virtual unsigned long Process(void* parameter) = 0;
WorkerThread有一个简单的消息循环,并展示了如何从ThreadWin继承。
class WorkerThread : public ThreadWin {
public:
WorkerThread(const CHAR* threadName) : ThreadWin(threadName) {}
private:
virtual unsigned long Process(void* parameter) {
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, WM_USER_BEGIN, WM_USER_END)) != 0) {
switch (msg.message) {
case WM_THREAD_MSG: {
ASSERT_TRUE(msg.wParam != NULL);
ThreadMsg* threadMsg = reinterpret_cast(msg.wParam);
cout << threadMsg->message.c_str() << endl;
delete threadMsg;
break;
}
case WM_EXIT_THREAD:
return 0;
default:
ASSERT();
}
}
return 0;
}
};
创建线程对象很容易。
WorkerThread workerThread1("WorkerThread1");
WorkerThread workerThread2("WorkerThread2");
CreateThread()用于创建线程,并强制创建消息队列。线程现在等待启动同步事件,然后进入Process()消息循环。
workerThread1.CreateThread();
workerThread2.CreateThread();
ThreadWin::StartAllThreads()同时启动所有系统线程。线程现在被允许进入Process()消息循环。
ThreadWin::StartAllThreads();
PostThreadMessage()向工作线程发送数据。
ThreadMsg* threadMsg = new ThreadMsg();
threadMsg->message = "Hello world!";
workerThread1.PostThreadMessage(WM_THREAD_MSG, threadMsg);
使用ExitThread()进行有序的线程退出和清理使用的资源。
workerThread1.ExitThread();
workerThread2.ExitThread();
ThreadWin::CreateThread()使用Win32 CreateThread() API创建一个线程。主线程入口函数是ThreadWin::RunProcess()。创建线程后,调用等待线程完成创建消息队列。
BOOL ThreadWin::CreateThread() {
if (!IsCreated()) {
m_hThreadStarted = CreateEvent(NULL, TRUE, FALSE, TEXT("ThreadCreatedEvent"));
ThreadParam threadParam;
threadParam.pThread = this;
m_hThread = ::CreateThread(NULL, 0, (unsigned long (__stdcall*)(void*))RunProcess, (void*)(&threadParam), 0, &m_threadId);
ASSERT_TRUE(m_hThread != NULL);
DWORD err = WaitForSingleObject(m_hThreadStarted, MAX_WAIT_TIME);
ASSERT_TRUE(err == WAIT_OBJECT_0);
CloseHandle(m_hThreadStarted);
m_hThreadStarted = INVALID_HANDLE_VALUE;
return m_hThread ? TRUE : FALSE;
}
return FALSE;
}
Microsoft的PostThreadMessage()函数文档解释了如何强制创建消息队列。如果不强制队列创建,PostThreadMessage()可能会随机失败,这取决于线程的初始化方式。可以通过创建一个线程,然后立即使用PostThreadMessage()向新线程发送消息来证明这一点。返回值将指示失败,因为线程没有足够的时间初始化消息队列。在CreateThread()和PostThreadMessage()之间放置一个Sleep(1000)可以使其工作,但这是脆弱的。ThreadWin可靠地解决了这个问题。
ThreadWin::RunProcess()现在在新线程上执行,并使用PeekMessage()强制队列创建。创建队列后,等待的ThreadWin::CreateThread()函数被释放。
int ThreadWin::RunProcess(void* threadParam) {
ThreadWin* thread;
thread = (ThreadWin*)(static_cast(threadParam)->pThread);
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
BOOL err = SetEvent(thread->m_hThreadStarted);
ASSERT_TRUE(err != 0);
if (thread->SYNC_START == TRUE) {
DWORD err = WaitForSingleObject(m_hStartAllThreads, MAX_WAIT_TIME);
ASSERT_TRUE(err == WAIT_OBJECT_0);
}
int retVal = thread->Process(NULL);
err = SetEvent(thread->m_hThreadExited);
ASSERT_TRUE(err != 0);
return retVal;
}
ThreadWin::StartAllThreads()被调用以释放所有等待的线程。
void ThreadWin::StartAllThreads() {
BOOL err = SetEvent(m_hStartAllThreads);
ASSERT_TRUE(err != 0);
}
void ThreadWin::ExitThread() {
if (m_hThread != INVALID_HANDLE_VALUE) {
m_hThreadExited = CreateEvent(NULL, TRUE, FALSE, TEXT("ThreadExitedEvent"));
PostThreadMessage(WM_EXIT_THREAD);
if (::WaitForSingleObject(m_hThreadExited, MAX_WAIT_TIME) == WAIT_TIMEOUT)
::TerminateThread(m_hThread, 1);
::CloseHandle(m_hThread);
m_hThread = INVALID_HANDLE_VALUE;
::CloseHandle(m_hThreadExited);
m_hThreadExited = INVALID_HANDLE_VALUE;
}
}