C语言CreateThread创建线程步骤是什么?

99ANYc3cd6
预计阅读时长 26 分钟
位置: 首页 C语言 正文

这是一个非常核心的Windows多线程编程知识点,我会从以下几个方面为你进行说明:

c语言createthread
(图片来源网络,侵删)
  1. 函数简介CreateThread 是什么?
  2. 函数原型与参数详解:每个参数代表什么意思?
  3. 完整代码示例:如何创建并运行一个线程?
  4. 关键注意事项:为什么新线程要调用 ExitThread 而不是 return?为什么主线程要调用 WaitForSingleObject
  5. 替代方案:为什么通常推荐使用 _beginthreadex 而不是 CreateThread

函数简介

CreateThread 是 Windows API (Application Programming Interface) 提供的一个函数,它属于 Windows.h 头文件,它的作用是在当前进程的地址空间中创建一个新的执行线程。

当你调用这个函数成功后,操作系统会为你分配一个新的线程,让它和你的主线程(main 函数所在的线程)并发地执行代码,这对于执行耗时任务(如文件下载、复杂计算)而不阻塞主线程(保持UI响应)非常有用。


函数原型与参数详解

CreateThread 的函数原型如下:

HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]            SIZE_T                 dwStackSize,
  [in]            LPTHREAD_START_ROUTINE lpStartAddress,
  [in, optional]  LPVOID                 lpParameter,
  [in]            DWORD                  dwCreationFlags,
  [out, optional] LPDWORD                lpThreadId
);
参数 类型 描述
lpThreadAttributes LPSECURITY_ATTRIBUTES 安全属性指针,通常传 NULL,表示使用默认安全描述符。
dwStackSize SIZE_T 线程的初始堆栈大小(字节),如果为 0,则使用默认大小(通常是 1MB)。
lpStartAddress LPTHREAD_START_ROUTINE 最重要的参数,这是一个函数指针,指向新线程开始执行的函数,这个函数必须遵循特定的签名:DWORD WINAPI ThreadFunc(LPVOID lpParam);
lpParameter LPVOID 传递给新线程函数的参数,你可以通过这个参数向新线程传递任何数据(指针、结构体等),如果不需要参数,可以传 NULL
dwCreationFlags DWORD 控制线程创建的标志,常用值:
- 0:创建后立即运行。
- CREATE_SUSPENDED:创建后挂起,直到调用 ResumeThread 才会运行。
lpThreadId LPDWORD 一个指向 DWORD 类型的变量,用于接收新线程的ID,如果不需要,可以传 NULL

返回值

c语言createthread
(图片来源网络,侵删)
  • 成功:返回一个 HANDLE 类型的句柄,代表新创建的线程,你需要保存这个句柄,以便后续管理线程(如等待、关闭)。
  • 失败:返回 NULL,可以通过调用 GetLastError() 函数获取具体的错误码。

完整代码示例

下面是一个完整的、可运行的C语言示例,它演示了如何创建一个线程,让它打印数字,然后主线程等待它完成。

#include <windows.h>
#include <stdio.h>
// 1. 定义线程函数的签名
// 线程函数必须返回一个 DWORD WINAPI,并且接收一个 LPVOID 参数
DWORD WINAPI ThreadFunc(LPVOID lpParam) 
{
    // lpParam 是主线程传递过来的参数
    int* pNum = (int*)lpParam;
    int count = *pNum;
    printf("新线程启动!将打印 %d 次,\n", count);
    for (int i = 0; i < count; i++) {
        printf("线程输出: %d\n", i + 1);
        // 模拟耗时操作
        Sleep(500); // 休眠500毫秒
    }
    printf("新线程结束!\n");
    // 2. 线程函数结束时,必须调用 ExitThread 或 return
    // return 在这里等同于 ExitThread
    return 0;
}
int main()
{
    HANDLE hThread;      // 用于存储线程句柄
    DWORD dwThreadId;    // 用于存储线程ID
    int threadParam = 5; // 传递给线程的参数
    printf("主线程:准备创建新线程...\n");
    // 3. 调用 CreateThread 创建线程
    hThread = CreateThread(
        NULL,                   // 默认安全属性
        0,                      // 默认堆栈大小
        ThreadFunc,             // 线程函数的地址
        &threadParam,           // 传递给线程函数的参数
        0,                      // 立即运行
        &dwThreadId             // 获取线程ID
    );
    // 检查线程是否创建成功
    if (hThread == NULL) {
        printf("创建线程失败!错误码: %d\n", GetLastError());
        return 1;
    }
    printf("主线程:新线程创建成功!线程ID: %d\n", dwThreadId);
    printf("主线程:现在主线程和新线程将同时运行...\n");
    // 4. 主线程等待新线程结束
    // WaitForSingleObject 会阻塞主线程,直到指定的对象(这里是我们的线程) signaled(结束或收到信号)
    WaitForSingleObject(hThread, INFINITE);
    printf("主线程:检测到新线程已结束,主线程继续执行,\n");
    // 5. 关闭线程句柄
    // 使用完句柄后,必须调用 CloseHandle 释放资源
    CloseHandle(hThread);
    printf("主线程:程序结束,\n");
    return 0;
}

如何编译和运行

  1. 将代码保存为 thread_example.c
  2. 打开 Visual Studio 的开发者命令提示 (x64 Native Tools Command Prompt)。
  3. 使用以下命令进行编译:
    cl thread_example.c /link
  4. 运行生成的 thread_example.exe

预期输出: 你会看到主线程和新线程的输出是交错进行的,这证明了它们是并发执行的。

主线程:准备创建新线程...
主线程:新线程创建成功!线程ID: 1234  // ID是随机的
主线程:现在主线程和新线程将同时运行...
新线程启动!将打印 5 次。
线程输出: 1
线程输出: 2
线程输出: 3
线程输出: 4
线程输出: 5
新线程结束!
主线程:检测到新线程已结束,主线程继续执行。
主线程:程序结束。

关键注意事项

a. 线程函数的返回:ExitThread vs return

在C/C++中,在线程函数内部使用 return 和调用 ExitThread() 效果是一样的,有一个非常重要的区别:

c语言createthread
(图片来源网络,侵删)
  • return:会调用该线程函数作用域内所有局部对象的析构函数。
  • ExitThread()不会调用任何局部对象的析构函数。

这可能导致资源泄漏(如没有关闭的文件句柄、没有释放的内存等),最佳实践是:

  • 在线程函数中,直接 return 即可,这样更符合C++的习惯,能保证局部对象被正确销毁。
  • ExitThread 主要用于在线程函数外部强制终止一个线程(不推荐,因为可能导致资源泄漏和程序不稳定)。

b. 主线程等待:WaitForSingleObject

为什么主线程需要等待新线程结束?

如果不等待,主线程可能在执行完 printf("主线程:程序结束,\n"); 后就退出了,当主线程退出时,整个进程就会被终止,导致新线程被“强行杀死”,它还没来得及完成的操作就会中断。

WaitForSingleObject(hThread, INFINITE); 的作用就是告诉主线程:“在这里等我,直到 hThread 代表的那个线程执行完毕你再继续走”,这确保了新线程有足够的时间完成它的任务。

c. 关闭句柄:CloseHandle

CreateThread 返回的 HANDLE 是一个内核对象,它不是线程本身,而是操作系统用来管理线程的一个“把手”或“引用”,当你不再需要这个句柄时(比如已经等待线程结束),必须调用 CloseHandle 来释放它,否则,会造成句柄泄漏,长期运行的程序可能会因为句柄耗尽而崩溃。


替代方案:_beginthreadex vs CreateThread

在C语言标准库(C Runtime Library, CRT)中,有一个功能与 CreateThread 非常相似的函数:_beginthreadex

强烈推荐在C/C++程序中使用 _beginthreadex 而不是 CreateThread

原因:C/C++运行时库的内部数据

C/C++运行时库(如 malloc, free, strtok, _crt_debugger_hook 等)使用了一些静态的或线程局部存储的数据来管理自己的状态。CreateThread 是一个纯粹的Windows API,它不知道这些CRT内部数据的存在。

当你使用 CreateThread 创建一个线程时:

  • 新线程开始运行。
  • 如果新线程调用了任何CRT函数(printf),这些函数可能会去访问或修改CRT的内部数据。
  • 如果多个线程同时访问这些共享数据,就会导致数据竞争不可预测的行为(程序崩溃、数据损坏等)。

_beginthreadex 的作用正是在创建线程之前,初始化这些CRT内部数据,为新线程创建一份独立的“上下文”,使用 _beginthreadex 可以保证CRT函数在线程环境下的安全性。

_beginthreadex 的用法与 CreateThread 几乎完全一样,只是参数和返回值类型略有不同(它返回一个 uintptr_t,但可以安全地转换为 HANDLE)。

使用 _beginthreadex 的示例

#include <windows.h>
#include <stdio.h>
#include <process.h> // 包含 _beginthreadex
// 线程函数签名不变
DWORD WINAPI ThreadFunc(LPVOID lpParam) 
{
    int* pNum = (int*)lpParam;
    int count = *pNum;
    printf("新线程启动!将打印 %d 次,\n", count);
    for (int i = 0; i < count; i++) {
        printf("线程输出: %d\n", i + 1);
        Sleep(500);
    }
    printf("新线程结束!\n");
    return 0;
}
int main()
{
    HANDLE hThread;
    unsigned int dwThreadId; // 注意这里是 unsigned int
    int threadParam = 5;
    printf("主线程:准备创建新线程...\n");
    // 使用 _beginthreadex
    hThread = (HANDLE)_beginthreadex(
        NULL,                   // 默认安全属性
        0,                      // 默认堆栈大小
        (unsigned int (__stdcall*)(void*))ThreadFunc, // 注意函数指针转换
        &threadParam,           // 传递给线程函数的参数
        0,                      // 立即运行
        &dwThreadId             // 获取线程ID
    );
    if (hThread == 0) { // 注意:失败时返回0
        printf("创建线程失败!错误码: %d\n", GetLastError());
        return 1;
    }
    printf("主线程:新线程创建成功!线程ID: %u\n", dwThreadId);
    printf("主线程:现在主线程和新线程将同时运行...\n");
    WaitForSingleObject(hThread, INFINITE);
    printf("主线程:检测到新线程已结束,主线程继续执行,\n");
    // 同样需要关闭句柄
    CloseHandle(hThread);
    printf("主线程:程序结束,\n");
    return 0;
}
特性 CreateThread (Windows API) _beginthreadex (CRT)
来源 Windows.h process.h
安全性 不安全,可能导致CRT内部数据竞争 安全,正确初始化了CRT环境
推荐度 不推荐在C/C++中使用 强烈推荐
线程结束 可使用 return 必须调用 _endthreadexreturn
资源清理 需要手动 CloseHandle 需要手动 CloseHandle

核心要点

  1. CreateThread 是Windows API,用于创建线程。
  2. 线程函数必须返回 DWORD WINAPI 并接收 LPVOID 参数。
  3. 主线程通常需要使用 WaitForSingleObject 等待子线程结束,以避免进程提前终止。
  4. 使用完句柄后,必须调用 CloseHandle 释放资源。
  5. 在C/C++编程中,请优先使用 _beginthreadex 以确保与C运行时库的兼容性和安全性。
-- 展开阅读全文 --
头像
抖音臭女与dede是什么关系?
« 上一篇 01-10
dede图集上一篇下一篇如何实现?
下一篇 » 01-10

相关文章

取消
微信二维码
支付宝二维码

目录[+]