Introduction
All operating systems provide services to create threads or tasks. On a Windows Win32 application, the main API is CreateThread()
. While it’s possible to use the raw Win32 functions, I find it better to encapsulate the behavior into a class that enforces the correct behaviors.
On multithreaded systems, you sometimes need a synchronized startup of all the threads. Depending on the design, if a worker thread starts processing too soon before other threads have had a chance to initialize, it can cause problems. Therefore, creating all threads first and simultaneously starting them with a synchronizing event solves this problem.
The Win32 thread API also has idiosyncrasies that, if not managed, can cause intermittent failures at runtime. One problem revolves around the message queue and when it’s created. After CreateThread()
is called, the message queue isn’t initialized immediately. The new thread needs a chance to run first. A PostThreadMessage()
to a thread without a message queue causes the function to fail.
For these reasons, when designing a Win32 application, it’s beneficial to use an encapsulating class to enforce correct behavior and prevent runtime errors.
Many wrapper class implementations exist for Win32 threads. However, I found none that solves the aforementioned problems. The ThreadWin
class provided here has the following benefits:
- Synchronized start – start all created threads simultaneously using a synchronization event
- Force create queue – force creating the thread message queue to prevent runtime errors
- Entry and exit – manage the thread resources and provide orderly startup and exit
- Ease of use – implement a single function
Process()
for a thread loop
See GitHub for latest source code:
Using the Code
ThreadWin
provides the Win32 thread encapsulation. The constructor allows naming the thread and controlling whether synchronized startup is desired.
class ThreadWin
{
public:
ThreadWin (const CHAR* threadName, BOOL syncStart = TRUE);
};
Inherit from the class and implement the pure virtual
function Process()
.
virtual unsigned long Process (void* parameter) = 0;
WorkerThread
has a simple message loop and shows how to inherit from 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<ThreadMsg*>(msg.wParam);
cout << threadMsg->message.c_str() << " " << GetThreadName() << endl;
delete threadMsg;
break;
}
case WM_EXIT_THREAD:
return 0;
default:
ASSERT();
}
}
return 0;
}
};
Creating thread objects is easy.
WorkerThread workerThread1("WorkerThread1");
WorkerThread workerThread2("WorkerThread2");
CreateThread()
is used to create the thread and forces the message queue to be created. The thread is now waiting for the startup synchronization event before entering the Process()
message loop.
workerThread1.CreateThread();
workerThread2.CreateThread();
ThreadWin::StartAllThreads()
starts all system threads at once. The threads are now allowed to enter the Process()
message loop.
ThreadWin::StartAllThreads();
PostThreadMessage()
sends data to the worker thread.
ThreadMsg* threadMsg = new ThreadMsg();
threadMsg->message = "Hello world!";
workerThread1.PostThreadMessage(WM_THREAD_MSG, threadMsg);
Use ExitThread()
for an orderly thread exit and cleanup used resources.
workerThread1.ExitThread();
workerThread2.ExitThread();
Implementation
ThreadWin::CreateThread()
creates a thread using the Win32 CreateThread()
API. The main thread entry function is ThreadWin::RunProcess()
. After the thread is created, the call blocks waiting for the thread to finish creating the message queue using WaitForSingleObject(m_hThreadStarted, MAX_WAIT_TIME)
.
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;
}
The Microsoft PostThreadMessasge()
function documentation explains how to force the message queue creation.
Quote:
In the thread to which the message will be posted, call PeekMessage as shown here to force the system to create the message queue.
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)
If you don’t force the queue creation, PostThreadMessage()
may fail randomly depending on how the threads initialize. You can prove this by creating a thread using CreateThread()
and immediately post to the new thread using PostThreadMessage()
. The return value will indicate failure since the thread wasn’t given enough time to initialize the message queue. Placing a Sleep(1000)
between CreateThread()
and PostThreadMessage()
makes it work, but its fragile. ThreadWin
reliably solves this problem.
ThreadWin::RunProcess()
now executes on the new thread of control and forces queue creation using PeekMessage()
. After queue creation, the waiting ThreadWin::CreateThread()
function is released using SetEvent(thread->m_hThreadStarted)
. If the thread instance wants a synchronized start, it now blocks using WaitForSingleObject(m_hStartAllThreads, MAX_WAIT_TIME)
.
int ThreadWin::RunProcess(void* threadParam)
{
ThreadWin* thread;
thread = (ThreadWin*)(static_cast<ThreadParam*>(threadParam))->pThread;
MSG msg;
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()
is called to release all waiting threads.
void ThreadWin::StartAllThreads()
{
BOOL err = SetEvent(m_hStartAllThreads);
ASSERT_TRUE(err != 0);
}
ThreadWin::ExitThread()
posts a WM_EXIT_THREAD
to the message queue to exit and waits for the thread to actually exit before returning using 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);
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;
}
}
Conclusion
ThreadWin
encapsulates the Win32 thread API in an easy to use class that enforces correct utilization and offers unique thread startup synchronization. Entry and exit features handle all the thread creation and destruction duties. The class standardizes thread usage and reduces common bugs associated with using Win32 worker threads.
History
- 22nd April, 2016
- 29th April, 2016
- Eliminated heap usage
- Updated article and attached source code