在 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 的线程状态和模块状态 。它有两种重载:- 创建工作者线程(传入一个函数指针)。
- 创建用户界面 (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)。
CreateThread和_beginthreadex:
这两种方式只初始化了 OS 和 CRT 的状态,没有初始化 MFC 的状态 。如果你在这两种线程中直接使用CString,当CString尝试获取 MFC 模块状态以分配内存或访问资源时,会因为状态指针为空或错误,导致断言失败 (Assert)、内存访问违规 (崩溃) 或内存泄漏。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 及以后) 开发中:
- 跨平台/现代 C++ 项目 :强烈建议使用 C++11 标准库的
std::thread。它底层在 Windows 上也是由_beginthreadex或CreateThread封装而来,但提供了类型安全、RAII 自动管理线程生命周期等现代特性。 - MFC 项目 :如果是纯 MFC 维护项目,依然使用
AfxBeginThread;如果 MFC 项目需要与大量现代 C++ 代码混合,可以使用std::thread,但在线程函数内部使用CString等 MFC 对象时,仍需手动调用AFX_MANAGE_STATE(AfxGetStaticModuleState())来保证 MFC 状态的正确性。