CreateThread、_beginthreadex、AfxBeginThread三种线程创建的区别与联系

   在 Windows 平台进行 C++ 开发时,经常需要创建线程,有的线程需要支持 CString,有的线程需要支持 TLS(线程本地存储)。这里介绍三种常见的线程创建方式:

   (1) CreateThread()

   (2) _beginthreadex()

   (3) AfxBeginThread()

一、 三种创建线程方式的区别与联系

1. 区别对比
特性 CreateThread() _beginthreadex() AfxBeginThread()
所属层级 Windows OS API (Win32) C/C++ 运行时库 (CRT) MFC 框架库
头文件 <windows.h> <process.h> <afxwin.h> (MFC)
初始化内容 仅初始化 OS 级别的线程内核对象。 初始化 OS 线程 + CRT 线程本地存储 (TLS)。 初始化 OS 线程 + CRT TLS + MFC 线程/模块状态。
适用场景 纯汇编、极度底层的系统级开发(不使用 C/C++ 标准库)。 纯 C/C++ 或 Win32 API 项目(非 MFC 环境)。 MFC 项目(特别是需要用到 MFC 类或 UI 消息循环时)。
返回值 线程句柄 (HANDLE) 线程句柄 (uintptr_t / HANDLE) 线程句柄 (CWinThread* 指针或 HANDLE)
2. 详细解析
  • CreateThread() :
    • 特点:最底层的 Windows API,直接由操作系统内核创建线程。
    • 致命缺点 :它不会 初始化 C/C++ 运行时库 (CRT) 的线程本地存储 (TLS)。如果你在这个线程里使用了 CRT 函数(如 malloc, free, strtok, errno 等),会导致内存泄漏、数据错乱甚至程序崩溃。(注:虽然较新的 MSVC 编译器对此做了一些兼容优化,但微软官方文档依然强烈不建议在 C/C++ 中直接调用它)。
  • _beginthreadex() :
    • 特点:微软为 C/C++ 开发者提供的标准线程创建函数。
    • 优点 :在底层调用 CreateThread 之前,它会先为当前线程分配并初始化 CRT 的 TLS 数据结构。这保证了多线程环境下使用 C/C++ 标准库函数(如 errno、strtok)的绝对安全。线程退出时,它也会正确清理 CRT 资源。
  • AfxBeginThread() :
    • 特点:MFC 框架专属的线程创建函数。
    • 优点 :不仅包含了 _beginthreadex 的所有功能,还初始化了 MFC 的线程状态和模块状态 。它有两种重载:
      1. 创建工作者线程(传入一个函数指针)。
      2. 创建用户界面 (UI) 线程 (传入一个 CWinThread 派生类,自动带消息循环)。
3. 它们的联系(调用链)

它们在底层是层层封装的关系:

  • AfxBeginThread() 内部最终会调用 _beginthreadex()(或直接调用 CreateThread 并手动补齐 MFC 初始化)。
  • _beginthreadex() 内部最终会调用 CreateThread()。

   总结 :在纯 C/C++ 中用 _beginthreadex,在 MFC 中用 AfxBeginThread。永远不要直接使用 CreateThread。


二、 哪种线程支持 MFC CString?

标准答案:只有 AfxBeginThread() 原生且完美支持 MFC CString。

为什么其他两种不支持?

CString 是 MFC 的核心类。MFC 的大多数类(包括 CString 的内存分配、字符串池管理、异常处理等)都严重依赖于 MFC 的模块状态 (Module State) 和 线程本地存储 (TLS)。

  1. CreateThread 和 _beginthreadex :
    这两种方式只初始化了 OS 和 CRT 的状态,没有初始化 MFC 的状态 。如果你在这两种线程中直接使用 CString,当 CString 尝试获取 MFC 模块状态以分配内存或访问资源时,会因为状态指针为空或错误,导致断言失败 (Assert)、内存访问违规 (崩溃) 或内存泄漏。
  2. AfxBeginThread :
    它在创建线程时,会自动将当前 MFC 模块的状态推入线程的 TLS 中。因此,在 AfxBeginThread 创建的线程里,CString 可以正确找到 MFC 的内存管理器和资源句柄,安全运行。
补充:如果非要在 _beginthreadex 中使用 CString 怎么办?

如果是维护老代码,或者因为某些原因必须用 _beginthreadex 创建线程,且又必须使用 CString,则需要在线程函数的第一行手动切换 MFC 模块状态:

cpp 复制代码
#include <afx.h>

UINT __stdcall MyThreadFunc(LPVOID pParam) 
{
    // 【关键】手动将当前线程的 MFC 模块状态设置为全局静态模块状态
    AFX_MANAGE_STATE(AfxGetStaticModuleState()); 

    CString strTest = _T("Hello MFC"); // 现在可以安全使用了
    // ... 其他 MFC 代码 ...
    
    return 0;
}

   注意:这种做法虽然可行,但非常繁琐,且如果线程中调用了其他 DLL 的 MFC 扩展,状态切换会变得极其复杂。因此,在 MFC 项目中,请务必使用 AfxBeginThread。


现代 C++ 开发建议

   虽然上述三种方式是 Windows 里创建线程的经典方式,但在现代 C++ (C++11 及以后) 开发中:

  1. 跨平台/现代 C++ 项目 :强烈建议使用 C++11 标准库的 std::thread 。它底层在 Windows 上也是由 _beginthreadex 或 CreateThread 封装而来,但提供了类型安全、RAII 自动管理线程生命周期等现代特性。
  2. MFC 项目 :如果是纯 MFC 维护项目,依然使用 AfxBeginThread;如果 MFC 项目需要与大量现代 C++ 代码混合,可以使用 std::thread,但在线程函数内部使用 CString 等 MFC 对象时,仍需手动调用 AFX_MANAGE_STATE(AfxGetStaticModuleState()) 来保证 MFC 状态的正确性。
相关推荐
sanqima1 小时前
依赖TLS和模块状态的MFC类
c++·mfc·tls·module state
All for pursuit.1 小时前
【动态规划-11】279.完全平方数
数据结构·c++·算法·leetcode·动态规划
瑶山2 小时前
Windows + WSL2 Ubuntu + VS Code配置C++开发环境
开发语言·c++·windows·vscode·wsl
小米里的大麦2 小时前
19 C、C++ 操作 MySQL
c语言·c++·mysql
有毒的教程2 小时前
Dev-C++ 使用扩展库两种方式:DevPak一键安装 / 手动配置(重点)
android·c++·adb
纪念 2292 小时前
c++ string(二)
开发语言·c++
sanqima2 小时前
MFC里的线程本地存储(TLS)与模块状态(Module State)
c++·mfc·tls·module state
见叶之秋2 小时前
【C++】从基类到派生类:关于C++继承的务实梳理
开发语言·c++
kyle~3 小时前
C++ 智能指针:RAII、所有权与对象生命周期
开发语言·c++