【Windows编程】创建线程
Windows 创建线程(CreateThread)笔记
1. 核心 API
1.1 CreateThread(Windows API)
1 | WINBASEAPI |
1.2 _beginthreadex(C 运行时库)
1 | _ACRTIMP uintptr_t __cdecl _beginthreadex( |
2. 详细参数表格
2.1 CreateThread 参数详解
| 参数名 | 类型 | 必填 | 含义 | 默认值(NULL/0时) | 注意事项 |
|---|---|---|---|---|---|
| lpThreadAttributes | LPSECURITY_ATTRIBUTES |
可选 | 线程句柄的安全描述符和继承性设置 | NULL → 使用默认安全描述符,返回的线程句柄不可继承 |
若需子进程继承线程句柄,需设置 bInheritHandle = TRUE |
| dwStackSize | SIZE_T |
必填 | 线程初始栈大小(字节) | 0 → 使用默认大小(通常为 1MB) |
实际分配的栈大小会四舍五入到页面大小倍数;系统会在需要时自动扩展栈 |
| lpStartAddress | LPTHREAD_START_ROUTINE |
必填 | 线程函数的入口点地址 | ⚠️ 无默认值,必须指定有效函数指针 | 线程函数签名:DWORD WINAPI ThreadProc(LPVOID lpParam); |
| lpParameter | LPVOID |
可选 | 传递给线程函数的自定义参数指针 | NULL → 线程函数接收 NULL |
可传递任意类型指针(结构体、对象等),但需确保参数生命周期覆盖线程执行期间 |
| dwCreationFlags | DWORD |
必填 | 控制线程创建的标志 | 0 → 线程创建后立即运行 |
常用标志:CREATE_SUSPENDED(挂起)、STACK_SIZE_PARAM_IS_A_RESERVATION(保留栈) |
| lpThreadId | LPDWORD |
可选(输出) | 接收新线程的唯一标识符(TID) | NULL → 不返回线程 ID |
可用于调试或后续操作(如 OpenThread);注意线程 ID 不一定是系统唯一(仅在本进程内唯一) |
2.2 _beginthreadex 参数详解
| 参数名 | 类型 | 必填 | 含义 | 默认值(NULL/0时) | 注意事项 |
|---|---|---|---|---|---|
| _Security | void* |
可选 | 同 CreateThread 的 lpThreadAttributes |
NULL → 默认安全描述符,句柄不可继承 |
内部会转换为 SECURITY_ATTRIBUTES* |
| _StackSize | unsigned |
必填 | 同 CreateThread 的 dwStackSize |
0 → 使用默认栈大小(1MB) |
实际行为与 CreateThread 相同 |
| _StartAddress | _beginthreadex_proc_type |
必填 | 线程函数入口点 | ⚠️ 无默认值 | 线程函数签名:unsigned __stdcall ThreadProc(void* arg);注意:必须使用 __stdcall 调用约定 |
| _ArgList | void* |
可选 | 同 CreateThread 的 lpParameter |
NULL → 线程函数接收 NULL |
同 CreateThread |
| _InitFlag | unsigned |
必填 | 同 CreateThread 的 dwCreationFlags |
0 → 立即运行 |
支持 CREATE_SUSPENDED 标志 |
| _ThrdAddr | unsigned* |
可选(输出) | 同 CreateThread 的 lpThreadId |
NULL → 不返回线程 ID |
同 CreateThread |
3. 两者的关键区别与选型建议
| 对比维度 | CreateThread(Windows API) |
_beginthreadex(CRT) |
|---|---|---|
| 所属库 | kernel32.dll |
msvcrt*.dll(C 运行时库) |
| 调用约定 | 线程函数:WINAPI(__stdcall) |
线程函数:__stdcall(但必须用 _beginthreadex_proc_type 类型) |
| 返回值类型 | HANDLE(内核句柄) |
uintptr_t(可转换为 HANDLE) |
| 线程 ID 类型 | DWORD |
unsigned int |
| C 运行时初始化 | ❌ 不会初始化 CRT(如 errno、strtok 等静态变量) |
✅ 会正确初始化 CRT,确保线程安全的库函数调用 |
| 内存管理 | 直接使用系统 API | 会额外分配 CRT 专用的线程本地存储(TLS)块 |
| 适用场景 | 纯 Windows API 编程,不依赖 C 运行时库 | C/C++ 程序,使用了 errno、strtok、malloc 等 CRT 函数 |
| 推荐程度 | ⚠️ 仅限纯系统编程 | ✅ 强烈推荐(除非项目完全不用 CRT) |
4. 使用要点与注意事项
4.1 通用要点
线程函数原型:
CreateThread:DWORD WINAPI ThreadProc(LPVOID lpParam)_beginthreadex:unsigned __stdcall ThreadProc(void* arg)- 两者返回值和参数类型略有不同,但功能等价
清理资源:
- 线程句柄(
HANDLE/uintptr_t)在使用完后必须调用CloseHandle()释放 _beginthreadex创建线程后,应配套使用_endthreadex()(而非ExitThread)以正确清理 CRT 资源
- 线程句柄(
错误处理:
CreateThread失败返回NULL,用GetLastError()获取错误码_beginthreadex失败返回0,用errno或_doserrno获取错误码
4.2 重要提醒 ⚠️
- 绝对不要在 C++ 程序中使用
CreateThread创建线程,除非你完全不使用 C 运行时库 - 使用
_beginthreadex时,线程退出应使用_endthreadex()(或让线程函数return),不要调用ExitThread或TerminateThread - 如果使用 C++ 的标准库特性(如
std::thread、std::async),直接使用标准库更安全,它们内部已正确封装了_beginthreadex
5. 代码示例
示例一
1 | int main() { |
示例二
1 | int main(int argc, char* argv[]) |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 星の夜!
