65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (10投票s)

2016年4月22日

CPOL

3分钟阅读

viewsIcon

29568

downloadIcon

740

一个用于同步线程启动和强制创建消息队列的 Win32 CreateThread() C++ 包装类。

引言

所有操作系统都提供创建线程或任务的服务。在 Windows Win32 应用程序中,主要的 API 是 CreateThread()。虽然可以使用原始的 Win32 函数,但我发现将行为封装到一个强制执行正确行为的类中更好。

在多线程系统中,有时需要所有线程同步启动。根据设计,如果一个工作线程在其他线程有机会初始化之前就开始处理,可能会导致问题。因此,先创建所有线程,然后使用同步事件同时启动它们可以解决这个问题。

Win32 线程 API 也有一些特殊之处,如果不加以管理,可能会导致运行时间歇性失败。一个问题围绕着消息队列及其创建时间。调用 CreateThread() 后,消息队列不会立即初始化。新线程需要有机会先运行。向没有消息队列的线程调用 PostThreadMessage() 会导致函数失败。

因此,在设计 Win32 应用程序时,使用一个封装类来强制执行正确的行为并防止运行时错误是有益的。

存在许多 Win32 线程的包装类实现。然而,我发现没有一个能解决上述问题。这里提供的 ThreadWin 类具有以下优点:

  1. 同步启动 - 使用同步事件同时启动所有创建的线程
  2. 强制创建队列 - 强制创建线程消息队列以防止运行时错误
  3. 入口和出口 - 管理线程资源并提供有序的启动和退出
  4. 易用性 - 实现一个单一的 Process() 函数用于线程循环

查看 GitHub 以获取最新源代码

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 日
    • 消除了堆使用
    • 更新了文章并附带了源代码
© . All rights reserved.