使用同步启动的 Win32 线程包装器






4.76/5 (10投票s)
一个用于同步线程启动和强制创建消息队列的 Win32 CreateThread() C++ 包装类。
引言
所有操作系统都提供创建线程或任务的服务。在 Windows Win32 应用程序中,主要的 API 是 CreateThread()
。虽然可以使用原始的 Win32 函数,但我发现将行为封装到一个强制执行正确行为的类中更好。
在多线程系统中,有时需要所有线程同步启动。根据设计,如果一个工作线程在其他线程有机会初始化之前就开始处理,可能会导致问题。因此,先创建所有线程,然后使用同步事件同时启动它们可以解决这个问题。
Win32 线程 API 也有一些特殊之处,如果不加以管理,可能会导致运行时间歇性失败。一个问题围绕着消息队列及其创建时间。调用 CreateThread()
后,消息队列不会立即初始化。新线程需要有机会先运行。向没有消息队列的线程调用 PostThreadMessage()
会导致函数失败。
因此,在设计 Win32 应用程序时,使用一个封装类来强制执行正确的行为并防止运行时错误是有益的。
存在许多 Win32 线程的包装类实现。然而,我发现没有一个能解决上述问题。这里提供的 ThreadWin
类具有以下优点:
- 同步启动 - 使用同步事件同时启动所有创建的线程
- 强制创建队列 - 强制创建线程消息队列以防止运行时错误
- 入口和出口 - 管理线程资源并提供有序的启动和退出
- 易用性 - 实现一个单一的
Process()
函数用于线程循环
查看 GitHub 以获取最新源代码
- 带同步启动的 Win32 线程包装器 - 作者:David Lafreniere
Using the Code
ThreadWin
提供了 Win32 线程封装。构造函数允许命名线程并控制是否需要同步启动。
class ThreadWin
{
public:
ThreadWin (const CHAR* threadName, BOOL syncStart = TRUE);
// …
};
继承此类并实现纯 virtual
函数 Process()
。
virtual unsigned long Process (void* parameter) = 0;
WorkerThread
包含一个简单的消息循环,并展示了如何继承 ThreadWin
。
class WorkerThread : public ThreadWin
{
public:
WorkerThread(const CHAR* threadName) : ThreadWin(threadName) {}
private:
/// The worker thread entry function
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);
// Get the ThreadMsg from the wParam value
ThreadMsg* threadMsg = reinterpret_cast<ThreadMsg*>(msg.wParam);
// Print the incoming message
cout << threadMsg->message.c_str() << " " << GetThreadName() << endl;
// Delete dynamic data passed through message queue
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()
将数据发送到工作线程。
// Create message to send to worker thread 1
ThreadMsg* threadMsg = new ThreadMsg();
threadMsg->message = "Hello world!";
// Post the message to worker thread 1
workerThread1.PostThreadMessage(WM_THREAD_MSG, threadMsg);
使用 ExitThread()
进行有序的线程退出和清理已使用的资源。
workerThread1.ExitThread();
workerThread2.ExitThread();
实现
ThreadWin::CreateThread()
使用 Win32 CreateThread()
API 创建线程。主线程入口函数是 ThreadWin::RunProcess()
。线程创建后,调用将阻塞,等待线程完成消息队列的创建,方法是调用 WaitForSingleObject(m_hThreadStarted, MAX_WAIT_TIME)
。
BOOL ThreadWin::CreateThread()
{
// Is the thread already created?
if (!IsCreated ())
{
m_hThreadStarted = CreateEvent(NULL, TRUE, FALSE, TEXT("ThreadCreatedEvent"));
// Create the worker thread
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);
// Block the thread until thread is fully initialized including message queue
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 的 PostThreadMessasge()
函数文档解释了如何强制创建消息队列。
引用在将消息发布的线程中,调用
PeekMessage
,如下所示,以强制系统创建消息队列。PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)
如果您不强制创建队列,PostThreadMessage()
可能会随机失败,具体取决于线程的初始化方式。您可以通过使用 CreateThread()
创建一个线程并立即使用 PostThreadMessage()
发布到新线程来证明这一点。返回值将指示失败,因为线程没有足够的时间初始化消息队列。在 CreateThread()
和 PostThreadMessage()
之间放置一个 Sleep(1000)
可以使其工作,但这很脆弱。ThreadWin
可靠地解决了这个问题。
ThreadWin::RunProcess()
现在在新线程上执行,并通过 PeekMessage()
强制创建队列。队列创建后,等待的 ThreadWin::CreateThread()
函数通过 SetEvent(thread->m_hThreadStarted)
被释放。如果线程实例需要同步启动,它现在将阻塞,调用 WaitForSingleObject(m_hStartAllThreads, MAX_WAIT_TIME)
。
int ThreadWin::RunProcess(void* threadParam)
{
// Extract the ThreadWin pointer from ThreadParam.
ThreadWin* thread;
thread = (ThreadWin*)(static_cast<ThreadParam*>(threadParam))->pThread;
// Force the system to create the message queue before setting the event below.
// This prevents a situation where another thread calls PostThreadMessage to post
// a message before this thread message queue is created.
MSG msg;
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
// Thread now fully initialized. Set the thread started event.
BOOL err = SetEvent(thread->m_hThreadStarted);
ASSERT_TRUE(err != 0);
// Using a synchronized start?
if (thread->SYNC_START == TRUE)
{
// Block the thread here until all other threads are ready. A call to
// StartAllThreads() releases all the threads at the same time.
DWORD err = WaitForSingleObject(m_hStartAllThreads, MAX_WAIT_TIME);
ASSERT_TRUE(err == WAIT_OBJECT_0);
}
// Call the derived class Process() function to implement the thread loop.
int retVal = thread->Process(NULL);
// Thread loop exited. Set exit event.
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);
}
ThreadWin::ExitThread()
向消息队列发布一个 WM_EXIT_THREAD
消息以退出,并在返回前使用 WaitForSingleObject (m_hThreadExited, MAX_WAIT_TIME)
等待线程实际退出。
void ThreadWin::ExitThread()
{
if (m_hThread != INVALID_HANDLE_VALUE)
{
m_hThreadExited = CreateEvent(NULL, TRUE, FALSE, TEXT("ThreadExitedEvent"));
PostThreadMessage(WM_EXIT_THREAD);
// Wait here for the thread to exit
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;
}
}
结论
ThreadWin
将 Win32 线程 API 封装在一个易于使用的类中,该类强制正确使用并提供独特的线程启动同步。入口和出口功能负责所有线程的创建和销毁。该类标准化了线程使用,并减少了使用 Win32 工作线程时常见的错误。
历史
- 2016 年 4 月 22 日
- 首次发布
- 2016 年 4 月 29 日
- 消除了堆使用
- 更新了文章并附带了源代码