【问题标题】:Multithreading with _beginthread and CreateThread使用 _beginthread 和 CreateThread 进行多线程
【发布时间】:2014-01-20 21:30:44
【问题描述】:

我尝试用 C++ 编写多线程 WIN32 应用程序,但由于我遇到了困难。 其中一个窗口过程创建了一个线程,它管理这个窗口的输出。如果这个窗口过程接收到一条消息(来自其他窗口过程),它应该把它传送给它们的线程。一开始我使用 _beginthread(...) 函数,什么不起作用。 然后我用 CreateThread(...) 函数尝试了它,它有效吗?我做错了什么? (我的英文不太好,希望你能理解我的问题)

带有 CreateThread(...) 的代码:

DWORD thHalloHandle; // global
HWND  hwndHallo;     // Hwnd of WndProc4
...
LRESULT APIENTRY WndProc4 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 static PARAMS params ;

 switch (message)
 {
    case WM_CREATE: {
        params.hwnd = hwnd ;
        params.cyChar = HIWORD (GetDialogBaseUnits ()) ;
        CreateThread(NULL, 0, thHallo, &params, 0, &thHalloHandle);
        return 0 ;
    }
...
    case WM_SPACE: {
        PostThreadMessage(thHalloHandle, WM_SPACE, 0, 0);
        return 0;
    }
...
}

带有 _beginthread(...) 的代码:

...
case WM_CREATE: {
   params.hwnd = hwnd ;
   params.cyChar = HIWORD (GetDialogBaseUnits ()) ;
   thHalloHandle = (DWORD)_beginthread (thHallo, 0, &params) ;
   return 0;
}
...
case WM_SPACE: {
   PostThreadMessage(thHalloHandle, WM_SPACE, 0, 0);
   return 0;
}
...

创建线程的 thHallo:

DWORD WINAPI thHallo(void *pvoid)
{
    static TCHAR *szMessage[] = { TEXT(...), ...};
    // Some Declaration
    pparams = (PPARAMS) pvoid;
    while(!pparams->bKill)
    {
      MsgReturn = GetMessage(&msg, NULL, 0, 0);
      hdc = GetDC(pparams->hwnd);
      if(MsgReturn)
      {
          switch(msg.message)
          {
             // case....
          }
      }
    }
    return 0;
}

thHallo for _beginthread(...):

void thHallo(void *pvoid)
{
   ...
   // The Same like for CreateThread
   ...
   _endthread();
}

【问题讨论】:

    标签: c++ winapi


    【解决方案1】:

    _beginthread/ex() 函数被证明是极难消除的。早在上个世纪就有必要,VS6 是最后一个需要它的 Visual Studio 版本。允许 CRT 为内部 CRT 变量分配线程局部状态是一种创可贴。与用于 strtok() 和 gmtime() 的函数一样,CRT 函数保持内部状态。该状态必须为每个线程单独存储,以便在一个线程中使用 strtok() 不会破坏在另一个线程中使用 strtok()。它必须以线程本地状态存储。 _beginthread/ex() 确保再次分配和清理此状态。

    这已经得到解决,当 Windows 2000 引入线程池时必然如此。当您的代码被线程池线程调用时,无法初始化内部 CRT 状态。顺便说一句,他们必须解决的最困难的问题是确保线程本地状态在线程停止运行时再次自动清理。许多程序因出现问题而死机,Apple 的 QuickTime 是这些崩溃的一个特别严重的来源。

    所以忘记 _beginthread() 曾经存在过,使用 CreateThread() 就可以了。

    您对 PostThreadMessage() 的使用存在严重问题。您在 _beginthread() 代码中使用了错误的参数,这就是它不起作用的原因。但它存在更大的问题。发布的消息只能在您的消息循环中检索。哪个工作正常,直到不再是您的消息循环正在发送消息。这在 GUI 应用程序中的许多情况下都会发生。简单的例子是使用 MessageBox()、DialogBox() 或用户调整窗口大小。由 Windows 自身运行的模式代码,用于泵送消息循环。

    一个大问题是该代码中的消息循环知道有关您发布的消息的 bean。他们只是掉进了比特桶,消失得无影无踪。该模态循环内的 DispatchMessage() 调用失败,您发布的消息具有 NULL 窗口句柄。

    必须改用 PostMessage() 来解决此问题。这需要一个窗口句柄。您可以使用 any 窗口句柄,主窗口的句柄是一个不错的选择。更好的是,您可以使用自己的 WndProc() 创建一个专用窗口,它只是不可见的,它只处理这些线程间消息。一个很常见的选择。 DispatchMessage() 现在不再失败,也解决了你的错误。

    【讨论】:

      【解决方案2】:

      您的问题是您需要一个TID(线程标识符)才能使用PostThreadMessage

      _beginthread 不返回 TID,而是返回线程句柄。

      解决方案是使用GetThreadId 函数。

      HANDLE hThread = (HANDLE)_beginthread (thHallo, 0, &params) ;
      thHalloHandle = GetThreadId( hThread );
      

      更好的代码(参见文档here

      HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, thHallo, &params, 0, &thHalloHandle ) ;
      

      【讨论】:

      • @DavidHeffernan 确实如此。我相信 OP 在阅读我们的答案后会重构代码。
      【解决方案3】:

      您对CreateThread 的调用会将线程ID 放入thHalloHandle。对_beginthread 的调用将线程句柄放入thHalloHandle

      现在,线程 ID 与线程句柄不同。当您调用 PostThreadMessage 时,您确实需要提供线程 ID。您只对 CreateThread 变体执行此操作,我相信这可以解释问题。

      您的代码缺少错误检查。如果您在调用PostThreadMessage 时检查了错误,您会发现PostThreadMessage 返回了FALSE。如果你接着打电话给GetLastError,那将返回ERROR_INVALID_THREAD_ID。我强烈建议您加入正确的错误检查。

      为了解决这个问题,您首先必须更清楚线程 ID 和线程句柄之间的区别。你应该给thHalloHandle 一个不同的名字:thHalloThreadId 也许。如果你想使用_beginthread,你必须调用GetThreadId,传递线程句柄,以获得线程ID。或者,使用产生线程 ID 的_beginthreadex,或者实际上是CreateThread

      【讨论】:

      • 不能让typcast成为_beginthread的返回值吗?我应该在哪里调用 GetThreadId?在 WndProc4 或 thHallo 中?
      • @T.C.您必须掌握的一点是_beginthread 返回线程句柄CreateThread 的最后一个参数产生 线程 ID。它们是完全不同的东西。再多的施法也不能把苹果变成橘子。您需要一个线程 ID。像你一样使用CreateThread。或者使用_beginthread,调用GetThreadId获取线程ID。或使用_beginthreadex。最重要的是,添加一些错误检查。 ;-)
      • 谢谢您,David Heffernan 您解释了我的实施中的问题。 _beginthread() 只向线程返回一个句柄,所以我必须使用 GetThreadId(HANDLE) 获取 TID 才能使用 PostThreadMessage(..) 发送。 CreateThread 在地址 lpThreadId 中产生 ID,所以我“自动”得到它...
      • @T.C.好的。现在你应该记住接受你认为最好的答案。您从 manuell 获得了相同的信息,由您决定您认为哪个答案更好。请回顾一下您之前的问题,因为我认为您也可以在那里接受一些答案。谢谢。
      • @T.C. CreateThread_beginthread[ex] 之间存在显着的区别。后者初始化 CRT 需要的每个线程状态 - 并在它终止时执行清理。如果您在线程上运行任何执行 CRT 功能的代码,则该线程必须使用_beginthread[ex] 创建。编写不使用 CRT 的代码几乎是不可能的。
      猜你喜欢
      • 2010-09-24
      • 2011-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 2018-10-15
      • 2012-03-17
      相关资源
      最近更新 更多