Windows 提供了一套强大的原生多线程 API,主要在 <windows.h> 头文件中定义,这套 API 功能全面,但相对底层,学习曲线稍陡,下面我将从核心概念、关键 API、同步机制到完整示例,一步步为你讲解。

核心概念与头文件
在开始之前,你需要了解几个核心概念:
- 线程: 程序执行的最小单位,一个进程可以拥有多个线程,它们共享进程的内存空间和资源。
- 句柄: Windows 中用来标识一个资源(如窗口、文件、线程、进程等)的整数,对于线程,我们使用
HANDLE类型。 - ID: 一个唯一的标识符,用来区分不同的线程,类型为
DWORD。 - 同步: 当多个线程访问共享资源时,需要一种机制来协调它们的执行顺序,以避免数据竞争和不一致,这是多线程编程中最重要也最复杂的问题。
必需的头文件:
#include <windows.h> // Windows API 的核心头文件 #include <stdio.h> // 用于标准输入输出
创建线程
创建新线程使用 CreateThread 函数,这是最核心的函数。
CreateThread 函数原型
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性,通常传 NULL SIZE_T dwStackSize, // 栈大小,0 表示使用默认大小 LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数的地址 LPVOID lpParameter, // 传递给线程函数的参数 DWORD dwCreationFlags, // 创建标志,0 表示立即运行 LPDWORD lpThreadId // 用于接收线程ID的指针,可传 NULL );
-
lpStartAddress: 这是最重要的参数,它是一个函数指针,指向你的线程函数,这个函数必须遵循特定的签名:
(图片来源网络,侵删)DWORD WINAPI ThreadFunction(LPVOID lpParam);
WINAPI: 是__stdcall的宏定义,规定了函数调用时的参数传递方式。LPVOID: 指向任意类型的指针,用于传递参数。- 返回值类型为
DWORD,可以看作是线程的“退出码”。
-
lpParameter: 你想传递给线程函数的任何数据,如果不需要传递数据,可以设为NULL。 -
返回值:
- 成功:返回一个新线程的
HANDLE。 - 失败:返回
NULL,你需要调用GetLastError()来获取具体的错误码。
- 成功:返回一个新线程的
线程函数与参数
线程函数是线程的“主心骨”,它包含了线程要执行的所有逻辑。
示例:一个简单的线程函数

// 线程函数
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
// 将 lpParam 转换为实际的数据类型
int threadNumber = (int)(DWORD_PTR)lpParam; // 使用 (DWORD_PTR) 避免编译器警告
for (int i = 0; i < 5; i++) {
printf("Hello from thread %d! Count: %d\n", threadNumber, i);
Sleep(500); // 休眠 500 毫秒
}
printf("Thread %d is finished.\n", threadNumber);
return 0; // 返回退出码
}
注意: lpParam 是一个 32 位或 64 位的指针,在 64 位系统上,直接将它强制转换为 int 可能会丢失高位信息,推荐使用 (DWORD_PTR) 或 INT_PTR 作为中间类型进行转换,这是 Windows API 推荐的做法。
等待与结束线程
主线程需要一种方式来等待子线程完成,或者在子线程结束后清理资源。
WaitForSingleObject
这是一个最基本的同步函数,可以让主线程“阻塞”,直到指定的对象(在这里是线程)变为“已 signaled”状态。
- 对于线程,当线程执行完毕后,它的状态就会变为 signaled。
INFINITE表示无限期等待。
HANDLE hThread = CreateThread(...);
if (hThread == NULL) {
// 错误处理
}
// 主线程在这里等待,直到 hThread 代表的线程执行完毕
WaitForSingleObject(hThread, INFINITE);
// 线程结束后,关闭其句柄以释放资源
CloseHandle(hThread);
ExitThread 和 TerminateThread
ExitThread(DWORD dwExitCode): 线程主动调用此函数来结束自己。不推荐使用,因为它不会执行 C/C++ 运行时库的清理工作(如全局对象的析构函数)。TerminateThread(HANDLE hThread, DWORD dwExitCode): 强制结束一个线程。极度不推荐使用!这会导致线程立即停止,无法释放其拥有的资源(如锁、内存等),极易造成死锁和数据损坏,只在极端紧急情况下考虑。
最佳实践:让线程函数自然执行完毕并返回。
线程同步(核心重点)
当多个线程读写同一块共享数据时,就会发生“数据竞争”,导致不可预测的结果,Windows 提供了多种同步对象来解决这个问题。
1 临界区
临界区是最轻量级的同步机制,它只用于同一进程内的线程同步。
- 特点:速度快,但不能跨进程。
- 原理:同一时间只允许一个线程进入临界区。
关键函数:
InitializeCriticalSection(&CriticalSection): 初始化临界区。EnterCriticalSection(&CriticalSection): 进入临界区,如果另一个线程已经进入,则当前线程会在此等待,直到临界区被释放。LeaveCriticalSection(&CriticalSection): 离开临界区,允许其他线程进入。DeleteCriticalSection(&CriticalSection): 删除临界区(在线程不再使用后)。
2 互斥量
互斥量与临界区功能类似,但它是一个内核对象,可以用于跨进程的线程同步。
- 特点:比临界区慢,但功能更强大。
- 原理:与临界区类似,但可以被操作系统调度。
关键函数:
CreateMutex(NULL, FALSE, NULL): 创建一个互斥量。WaitForSingleObject(hMutex, INFINITE): 等待并获取互斥量所有权。ReleaseMutex(hMutex): 释放互斥量所有权。CloseHandle(hMutex): 关闭互斥量句柄。
3 事件
事件是一个内核同步对象,用于线程间的通知。
- 特点:一个线程可以设置事件,另一个或多个线程可以等待事件被设置。
- 类型:
- 自动重置事件: 当一个等待线程被释放后,事件会自动自动变回非 signaled 状态。
- 手动重置事件: 当一个等待线程被释放后,事件会保持 signaled 状态,直到有人手动将其重置。
关键函数:
CreateEvent(NULL, FALSE, FALSE, NULL): 创建一个自动重置、初始状态为 nonsignaled 的事件。SetEvent(hEvent): 将事件设置为 signaled 状态。ResetEvent(hEvent): 将事件重置为 nonsignaled 状态。WaitForSingleObject(hEvent, INFINITE): 等待事件变为 signaled。
4 信号量
信号量用于控制对有限数量资源的访问。
- 特点:它维护一个计数器。
WaitForSingleObject会使计数器减一,ReleaseSemaphore会使计数器加一,只有当计数器大于0时,等待才能成功。 - 用途:控制一个最多有5个线程的线程池。
完整示例:生产者-消费者模型
这个例子综合了线程创建、参数传递、临界区同步等知识点。
场景:一个“生产者”线程向一个共享队列添加数据,一个“消费者”线程从队列中取出数据,为了安全访问队列,我们使用临界区。
#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE 5
// 共享数据结构
typedef struct {
int buffer[BUFFER_SIZE];
int in; // 生产者写入位置
int out; // 消费者读取位置
CRITICAL_SECTION cs; // 临界区,用于保护共享数据
} SharedData;
// 生产者线程函数
DWORD WINAPI ProducerThread(LPVOID lpParam) {
SharedData* data = (SharedData*)lpParam;
for (int i = 0; i < 10; i++) {
// 模拟生产数据
int item = i * 100;
// 进入临界区
EnterCriticalSection(&data->cs);
// 检查队列是否已满(简单示例,未做循环队列处理)
if (data->in < BUFFER_SIZE) {
data->buffer[data->in] = item;
printf("Producer: Produced item %d at position %d\n", item, data->in);
data->in++;
} else {
printf("Producer: Buffer is full!\n");
}
// 离开临界区
LeaveCriticalSection(&data->cs);
Sleep(100); // 模拟生产耗时
}
return 0;
}
// 消费者线程函数
DWORD WINAPI ConsumerThread(LPVOID lpParam) {
SharedData* data = (SharedData*)lpParam;
int item;
for (int i = 0; i < 10; i++) {
// 进入临界区
EnterCriticalSection(&data->cs);
// 检查队列是否为空
if (data->out < data->in) {
item = data->buffer[data->out];
printf("Consumer: Consumed item %d from position %d\n", item, data->out);
data->out++;
} else {
printf("Consumer: Buffer is empty!\n");
}
// 离开临界区
LeaveCriticalSection(&data->cs);
Sleep(200); // 模拟消费耗时
}
return 0;
}
int main() {
HANDLE hProducer, hConsumer;
SharedData data = { {0}, 0, 0 };
// 初始化临界区
InitializeCriticalSection(&data.cs);
// 创建生产者线程
hProducer = CreateThread(NULL, 0, ProducerThread, &data, 0, NULL);
if (hProducer == NULL) {
printf("Failed to create producer thread.\n");
return 1;
}
// 创建消费者线程
hConsumer = CreateThread(NULL, 0, ConsumerThread, &data, 0, NULL);
if (hConsumer == NULL) {
printf("Failed to create consumer thread.\n);
CloseHandle(hProducer);
return 1;
}
// 等待两个线程都执行完毕
WaitForSingleObject(hProducer, INFINITE);
WaitForSingleObject(hConsumer, INFINITE);
// 清理资源
CloseHandle(hProducer);
CloseHandle(hConsumer);
DeleteCriticalSection(&data.cs);
printf("All threads finished.\n");
return 0;
}
现代 C++ 的替代方案 (std::thread)
如果你使用的是支持 C++11 或更高版本的编译器,强烈推荐使用 std::thread,而不是原生的 Windows API。std::thread 是跨平台的,代码更简洁、更安全、更易于维护。
使用 std::thread 重写上面的生产者-消费者示例:
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <queue>
std::mutex mtx; // C++ 的互斥量,对应 Windows 的临界区
std::queue<int> data_queue;
void producer() {
for (int i = 0; i < 10; ++i) {
int item = i * 100;
{
std::lock_guard<std::mutex> lock(mtx); // 自动加锁,作用域结束自动解锁
data_queue.push(item);
std::cout << "Producer: Produced item " << item << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer() {
for (int i = 0; i < 10; ++i) {
int item = -1;
{
std::lock_guard<std::mutex> lock(mtx);
if (!data_queue.empty()) {
item = data_queue.front();
data_queue.pop();
std::cout << "Consumer: Consumed item " << item << std::endl;
} else {
std::cout << "Consumer: Buffer is empty!" << std::endl;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
std::cout << "All threads finished." << std::endl;
return 0;
}
对比:可以看到,std::thread + std::mutex 的代码更短,可读性更高,并且没有 CloseHandle、DeleteCriticalSection 等繁琐的清理工作。
| 特性 | Windows API (<windows.h>) |
C++11 std::thread |
|---|---|---|
| 平台 | 仅 Windows | 跨平台 |
| 易用性 | 较低,API 较多,需要手动管理句柄 | 高,语法简洁,RAII 管理资源 |
| 性能 | 原生性能高 | 通常有良好优化,性能接近原生 |
| 同步 | 临界区、互斥量、事件、信号量等 | std::mutex, std::condition_variable 等 |
| 推荐场景 | 纯 C 语言项目。 需要调用特定 Windows 线程 API。 对性能有极致要求且需要精细控制。 |
C++ 项目(强烈推荐)。 需要代码可移植性。 追求开发效率和代码安全。 |
对于新的 C++ 项目,请优先选择 std::thread,对于纯 C 语言项目或在特定 Windows 环境下,掌握 Windows 原生线程 API 是一项非常重要的技能。
