Windows 提供了一套强大的多线程 API,主要包含在 <windows.h> 头文件中,这套 API 非常成熟和稳定,是进行 Windows 平台多线程开发的基础。

核心概念与准备
在开始之前,你需要了解几个核心概念:
- 进程:一个应用程序的实例,拥有独立的内存空间和系统资源。
- 线程:进程内的一个执行单元,是 CPU 调度的基本单位,一个进程可以包含多个线程,它们共享进程的资源(如内存、文件句柄等)。
- 句柄:Windows 中用来标识资源的唯一整数,线程、进程、文件、窗口等都是资源,都有对应的句柄。
- 同步:当多个线程访问共享资源时,需要采取某种机制来协调它们的执行顺序,以避免数据竞争和不一致的结果,常用的同步机制有:互斥量、临界区、事件、信号量等。
准备工作:
- 包含头文件:在你的 C 源文件中,必须包含
<windows.h>。#include <windows.h> #include <stdio.h> // 用于 printf
- 链接库:Windows 的多线程 API 是操作系统自带的,在 Visual Studio 中创建项目时默认会链接所需的库(如
kernel32.lib),如果你使用其他编译器(如 MinGW),可能需要手动指定链接库,但通常也不需要。
创建和终止线程
1 创建线程:CreateThread
这是创建线程最核心的函数。
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性,通常为 NULL SIZE_T dwStackSize, // 线程栈大小,0 表示使用默认大小 LPTHREAD_START_ROUTINE lpStartAddress, // 线程的起始函数地址 LPVOID lpParameter, // 传递给线程函数的参数 DWORD dwCreationFlags, // 创建标志,0 表示立即运行,CREATE_SUSPENDED 表示挂起 LPDWORD lpThreadId // 用于接收线程 ID 的指针,可以为 NULL );
- 返回值:如果成功,返回一个新线程的句柄;如果失败,返回
NULL,你需要通过GetLastError()获取错误码。 - 线程函数:
lpStartAddress指向的函数必须是以下原型:DWORD WINAPI ThreadFunction(LPVOID lpParam);
WINAPI是一个调用约定宏,确保参数传递方式正确。LPVOID lpParam是一个指向任意类型数据的指针,你可以用它来向线程传递参数。- 函数的返回值 (
DWORD) 会被GetExitCodeThread用来获取线程的退出码。
2 线程函数示例
// 线程函数
DWORD WINAPI ThreadFunc(LPVOID lpParam) {
// 将 lpParam 转换为我们需要的类型
int threadNum = *(int*)lpParam;
for (int i = 0; i < 5; i++) {
printf("Hello from thread %d! Count: %d\n", threadNum, i);
Sleep(500); // 休眠 500 毫秒
}
// 线程结束,返回一个退出码
return threadNum * 100;
}
3 等待线程结束:WaitForSingleObject
主线程需要一种方式来等待子线程完成,否则主线程可能执行完毕,导致整个进程退出,子线程被强制终止。

DWORD WaitForSingleObject( HANDLE hHandle, // 要等待的对象句柄(这里是线程句柄) DWORD dwMilliseconds // 等待时间,INFINITE 表示无限等待 );
- 返回值:
WAIT_OBJECT_0:对象 signaled 状态(对于线程,意味着已经结束)。WAIT_TIMEOUT:等待超时。WAIT_FAILED:等待失败。
4 获取线程退出码:GetExitCodeThread
BOOL GetExitCodeThread( HANDLE hThread, // 线程句柄 LPDWORD lpExitCode // 用于接收退出码的 DWORD 指针 );
- 如果线程仍在运行,
lpExitCode会被设置为STILL_ACTIVE。
5 关闭线程句柄:CloseHandle
当你不再需要使用线程句柄时(在等待线程结束后),应该关闭它,以释放系统资源。
BOOL CloseHandle(HANDLE hObject);
完整示例:创建和等待线程
这个例子演示了如何创建两个线程,并等待它们完成。
#include <windows.h>
#include <stdio.h>
// 线程函数
DWORD WINAPI ThreadFunc(LPVOID lpParam) {
int threadNum = *(int*)lpParam;
for (int i = 0; i < 5; i++) {
printf("Thread %d is running... Count: %d\n", threadNum, i);
Sleep(500); // 休眠 500 毫秒
}
printf("Thread %d is finished.\n", threadNum);
return threadNum * 100; // 返回一个退出码
}
int main() {
HANDLE hThread1, hThread2;
DWORD threadId1, threadId2;
int param1 = 1, param2 = 2;
DWORD exitCode1, exitCode2;
// 创建第一个线程
hThread1 = CreateThread(
NULL, // 默认安全属性
0, // 默认堆栈大小
ThreadFunc, // 线程函数
¶m1, // 传递给线程函数的参数
0, // 立即运行
&threadId1 // 接收线程ID
);
if (hThread1 == NULL) {
printf("CreateThread failed for thread 1. Error: %d\n", GetLastError());
return 1;
}
// 创建第二个线程
hThread2 = CreateThread(
NULL, // 默认安全属性
0, // 默认堆栈大小
ThreadFunc, // 线程函数
¶m2, // 传递给线程函数的参数
0, // 立即运行
&threadId2 // 接收线程ID
);
if (hThread2 == NULL) {
printf("CreateThread failed for thread 2. Error: %d\n", GetLastError());
CloseHandle(hThread1); // 清理已创建的句柄
return 1;
}
printf("Main thread: Created threads %d and %d.\n", threadId1, threadId2);
// 等待两个线程都执行完毕
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
printf("Main thread: Both threads have finished.\n");
// 获取并打印线程的退出码
GetExitCodeThread(hThread1, &exitCode1);
GetExitCodeThread(hThread2, &exitCode2);
printf("Thread 1 exited with code: %d\n", exitCode1);
printf("Thread 2 exited with code: %d\n", exitCode2);
// 关闭线程句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
return 0;
}
编译与运行 (Visual Studio):
- 创建一个新的 "Windows Desktop Application" 项目。
- 将上述代码粘贴到
main.c文件中。 - 按
F5运行。
编译与运行 (MinGW/GCC):

gcc -o thread_example thread_example.c -lkernel32 ./thread_example
线程同步
当多个线程需要访问同一个共享资源(如一个全局变量)时,就会发生数据竞争,导致不可预知的结果,Windows 提供了多种同步对象来解决此问题。
1 临界区
临界区是最快的同步机制,但它只能用于同一进程内的线程同步,它不能跨进程使用。
- 特点:速度快,占用资源少。
- 使用:
- 声明一个
CRITICAL_SECTION变量。 - 初始化
InitializeCriticalSection。 - 进入临界区
EnterCriticalSection(会阻塞)。 - 离开临界区
LeaveCriticalSection。 - 不用时删除
DeleteCriticalSection。
- 声明一个
示例:
#include <windows.h>
#include <stdio.h>
long g_sharedCounter = 0;
CRITICAL_SECTION g_cs; // 声明临界区
DWORD WINAPI CounterThread(LPVOID lpParam) {
for (int i = 0; i < 100000; i++) {
EnterCriticalSection(&g_cs); // 进入临界区
g_sharedCounter++; // 安全地修改共享数据
LeaveCriticalSection(&g_cs); // 离开临界区
}
return 0;
}
int main() {
HANDLE hThread[4];
InitializeCriticalSection(&g_cs); // 初始化临界区
// 创建4个线程
for (int i = 0; i < 4; i++) {
hThread[i] = CreateThread(NULL, 0, CounterThread, NULL, 0, NULL);
}
// 等待所有线程完成
WaitForMultipleObjects(4, hThread, TRUE, INFINITE);
// 关闭句柄
for (int i = 0; i < 4; i++) {
CloseHandle(hThread[i]);
}
printf("Final counter value: %ld\n", g_sharedCounter); // 应该是 400000
DeleteCriticalSection(&g_cs); // 删除临界区
return 0;
}
2 互斥量
互斥量与临界区功能类似,但它是一个内核对象,可以用于跨进程同步,这意味着它比临界区慢,但功能更强大。
- 特点:可以跨进程,速度比临界区慢。
- 使用:
- 声明一个
HANDLE类型的互斥量变量。 - 创建
CreateMutex。 - 等待互斥量
WaitForSingleObject。 - 释放互斥量
ReleaseMutex。 - 关闭句柄
CloseHandle。
- 声明一个
示例:
(代码结构与临界区示例类似,只需将 CRITICAL_SECTION 相关的调用替换为 HANDLE 互斥量的相关调用)
HANDLE g_hMutex; // 在线程函数中 WaitForSingleObject(g_hMutex, INFINITE); // 等待 g_sharedCounter++; ReleaseMutex(g_hMutex); // 释放 // 在 main 函数中 g_hMutex = CreateMutex(NULL, FALSE, NULL); // 创建 // ... 等待线程 ... CloseHandle(g_hMutex); // 关闭
3 信号量
信号量用于控制同时访问某个资源的线程数量,它维护一个计数器。
- 特点:允许多个线程同时访问,但数量有限。
- 使用:
- 创建
CreateSemaphore,指定初始计数和最大计数。 - 等待信号量
WaitForSingleObject(计数减1)。 - 释放信号量
ReleaseSemaphore(计数加1)。
- 创建
4 事件
事件用于线程间的通知,一个线程可以等待一个事件被“触发”(signaled)。
- 特点:用于通知,而不是锁定。
- 使用:
- 创建
CreateEvent。 - 等待事件
WaitForSingleObject。 - 触发事件
SetEvent(手动重置)或PulseEvent(脉冲)。
- 创建
线程终止
- 自然退出:线程函数执行完毕并返回,这是最推荐的方式。
ExitThread:线程主动调用此函数退出。这会导致栈上的局部变量不被清理,通常不推荐使用。TerminateThread:强制终止一个线程。这是非常危险的操作! 它不会释放线程拥有的资源(如锁、内存句柄等),可能导致整个进程处于不稳定状态,应仅在极端紧急情况下使用。
现代 C++ 的替代方案 (C++11)
如果你使用的是支持 C++11 或更高版本的编译器(如 Visual Studio 2012+ 或 GCC 4.8+),强烈推荐使用 C++11 的 <thread> 库,它提供了更安全、更面向对象、跨平台的接口,避免了 Windows API 中容易出错的句柄管理问题。
C++11 示例:
#include <iostream>
#include <thread>
#include <vector>
void thread_func(int id) {
for (int i = 0; i < 5; ++i) {
std::cout << "Hello from thread " << id << "! Count: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
int main() {
std::vector<std::thread> threads;
// 创建并启动多个线程
for (int i = 1; i <= 4; ++i) {
threads.emplace_back(thread_func, i);
}
std::cout << "Main thread: Created 4 threads." << std::endl;
// 等待所有线程完成
for (auto& th : threads) {
if (th.joinable()) {
th.join();
}
}
std::cout << "Main thread: All threads have finished." << std::endl;
return 0;
}
C++11 的优势:
- 自动管理:
std::thread对象在析构时会自动join()(如果设置了joinable)。 - 类型安全:传递参数时类型安全,避免了
LPVOID的强制转换。 - 跨平台:同样的代码可以在 Linux 和 macOS 上编译运行。
- 丰富的库:提供了
std::mutex,std::lock_guard,std::future,std::async等高级并发工具。
| 特性 | Windows API (<windows.h>) |
C++11 (<thread>) |
|---|---|---|
| 易用性 | 较低,需要手动管理句柄和错误检查 | 高,面向对象,RAII(资源获取即初始化) |
| 安全性 | 较低,容易忘记 CloseHandle 或 WaitFor... |
高,对象生命周期管理清晰 |
| 跨平台 | 否,仅限 Windows | 是,代码可移植到 Linux, macOS 等 |
| 性能 | 原生 API,性能极佳 | 通常有极小的额外开销,但现代实现已非常高效 |
| 推荐场景 | 维护旧的 Windows C 代码。 需要使用 Windows 特有的高级功能(如 I/O 完成端口)。 |
新项目,尤其是 C++ 项目。 需要代码可移植性。 希望使用更现代、更安全的并发模型。 |
对于新的 C++ 项目,请优先选择 C++11 的 <thread> 库,对于纯 C 语言项目或需要深度集成 Windows 平台的场景,Windows API 依然是强大而可靠的选择。
