Click here to Skip to main content
15,030,318 members
Articles / Desktop Programming / Win32
Article
Posted 22 Apr 2016

Stats

18.1K views
608 downloads
19 bookmarked

Win32 Thread Wrapper with Synchronized Start

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
23 Oct 2019CPOL3 min read
A Win32 CreateThread() C++ wrapper class for synchronized thread startup and forced message queue creation.

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:

  1. Synchronized start – start all created threads simultaneously using a synchronization event
  2. Force create queue – force creating the thread message queue to prevent runtime errors
  3. Entry and exit – manage the thread resources and provide orderly startup and exit
  4. Ease of use – implement a single function Process() for a thread loop

Using the Code

ThreadWin provides the Win32 thread encapsulation. The constructor allows naming the thread and controlling whether synchronized startup is desired.

C++
class ThreadWin 
{
public:
    ThreadWin (const CHAR* threadName, BOOL syncStart = TRUE);
    // …
};

Inherit from the class and implement the pure virtual function Process().

C++
virtual unsigned long Process (void* parameter) = 0;

WorkerThread has a simple message loop and shows how to inherit from ThreadWin.

C++
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;
    }
};

Creating thread objects is easy.

C++
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.

C++
workerThread1.CreateThread();
workerThread2.CreateThread();

ThreadWin::StartAllThreads() starts all system threads at once. The threads are now allowed to enter the Process() message loop.

C++
ThreadWin::StartAllThreads();

PostThreadMessage() sends data to the worker thread.

C++
// 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);

Use ExitThread() for an orderly thread exit and cleanup used resources.

C++
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).

C++
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;
}

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).

C++
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() is called to release all waiting threads.

C++
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).

C++
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;
    }
}

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
    • Initial release
  • 29th April, 2016
    • Eliminated heap usage
    • Updated article and attached source code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

David Lafreniere
United States United States
I've been a professional software engineer for over 20 years. When not writing code, I enjoy spending time with the family, camping and riding motorcycles around Southern California.

Comments and Discussions

 
GeneralGood Work Pin
Rick York24-Oct-19 5:50
mveRick York24-Oct-19 5:50 
GeneralRe: Good Work Pin
David Lafreniere25-Oct-19 5:24
MemberDavid Lafreniere25-Oct-19 5:24 
QuestionWhy using m_hThreadExited Pin
vipako24-Oct-19 2:12
Membervipako24-Oct-19 2:12 
Thread handle is kernel object, that mean it is signaled if thread exits. Instead of waiting for m_hThreadExited in function void ThreadWin::ExitThread(), you can directly wait for Thread handle:
::WaitForSingleObject (m_hThread, MAX_WAIT_TIME)
QuestionWhy using heap? Pin
waleri28-Apr-16 22:41
Memberwaleri28-Apr-16 22:41 
AnswerRe: Why using heap? Pin
David Lafreniere29-Apr-16 3:00
MemberDavid Lafreniere29-Apr-16 3:00 
GeneralRe: Why using heap? Pin
Member 1249354929-Apr-16 6:41
MemberMember 1249354929-Apr-16 6:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.