优先选std::thread,因其自动管理句柄、强制join/detach检查、初始化crt;裸用createthread易致句柄泄漏、crt崩溃或野指针,仅在需自定义栈大小、挂起控制或内核对象集成时选用。

std::thread,除非你明确需要控制线程栈大小、挂起/恢复、或与内核同步对象深度集成**。用 CreateThread 不是不行,但容易漏掉关键清理步骤,导致句柄泄漏或程序崩溃。
为什么 std::thread 比 CreateThread 更安全?
std::thread 是 C++11 标准的一部分,在 MSVC 中底层确实调用了 CreateThread,但它封装了三件你必须手动处理的事:句柄自动管理、析构时强制 join/detach 检查、C 运行时环境初始化。
而裸用 CreateThread 时:
- 忘记调用
CloseHandle→ 句柄泄漏(Windows 单进程默认上限 2048) - 线程函数里调用
malloc/printf但没初始化 CRT → 随机崩溃(尤其在调试版) - 主线程提前退出,子线程还在跑 →
std::cout等全局对象可能已被析构,输出乱码或 crash
CreateThread 的正确写法长什么样?
如果你真得用它(比如要指定栈大小为 1MB,或创建后先挂起再调度),必须严格遵循以下顺序:
- 线程函数签名必须是
DWORD WINAPI ThreadFunc(LPVOID),不能是void*或普通void - 传参用
LPVOID,别直接传局部变量地址(栈变量在线程启动前可能已销毁) - 创建后立即检查返回值:
if (hThread == NULL),而不是== INVALID_HANDLE_VALUE(CreateThread失败返回NULL) - 等待结束用
WaitForSingleObject(hThread, INFINITE),不是sleep或轮询 - 最后必须调用
CloseHandle(hThread),且只调一次 —— 句柄关闭后再次CloseHandle是未定义行为
示例关键片段:
HANDLE h = CreateThread(nullptr, 1024*1024, ThreadFunc, &data, CREATE_SUSPENDED, &tid);
if (h) {
ResumeThread(h); // 挂起后记得恢复
WaitForSingleObject(h, INFINITE);
CloseHandle(h); // 必须有
}
_beginthreadex 和 CreateThread 有什么区别?
这是最容易混淆的点。_beginthreadex 本质是微软 CRT 对 CreateThread 的包装,它做了两件事:自动初始化当前线程的 CRT 环境(比如 errno、printf 缓冲区)、允许线程函数返回 unsigned int 并由 CRT 自动转成 exit code。
所以:
- 如果你线程里只调 Windows API(
WriteFile、WaitForSingleObject),CreateThread足够 - 但只要用了
malloc、fopen、std::string构造、甚至std::cout,就必须用_beginthreadex,否则可能崩溃或内存泄漏 -
_beginthreadex返回的是uintptr_t,需强转为HANDLE才能和WaitForSingleObject配合
std::thread 的坑怎么避开?
std::thread 看似简单,但两个地方极易翻车:
- 对象离开作用域时仍是
joinable()→ 触发std::terminate,整个进程退出。必须显式t.join()或t.detach() - 传参是值拷贝,不是引用 ——
std::thread(func, obj)会拷贝obj;要传引用得用std::ref(obj),否则修改不到原对象 - 捕获 lambda 到线程里时,确保引用的对象生命周期 >= 线程运行时间,否则访问野指针
安全写法示例:
std::thread t([](int& x) { x++; }, std::ref(counter));
t.join(); // 必须
真正麻烦的从来不是“怎么创建”,而是“怎么不出错地收尾”。Windows 线程句柄、CRT 初始化、C++ 对象生命周期,这三者交叉时,一个疏忽就让程序在压力测试时才暴露问题。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











