本文内容基于微软官方文档整理,主要介绍 Windows 进程与线程的核心概念、API 使用方法及代码示例。
一、核心概念
1.1 进程(Process)
进程是正在执行的程序实例。每个进程都提供执行程序所需的资源,包括:
| 组成部分 | 说明 |
|---|---|
| 虚拟地址空间 | 进程独立的内存空间 |
| 可执行代码 | 程序的二进制指令 |
| 系统对象句柄 | 打开的文件、事件等 |
| 安全上下文 | 进程的权限标识 |
| 唯一进程标识符(PID) | 系统分配的唯一 ID |
| 环境变量 | 进程运行环境配置 |
| 优先级类 | 调度优先级 |
| 工作集大小 | 最小/最大内存占用 |
| 至少一个执行线程 | 主线程 |
💡 每个进程都以单个线程(主线程)启动,但可以从其任何线程创建其他线程。
1.2 线程(Thread)
线程是进程内可调度执行的实体,是操作系统分配处理器时间的基本单位。
线程独有的资源:
- 异常处理程序
- 调度优先级
- 线程本地存储(TLS)
- 唯一线程标识符(TID)
- 线程上下文(寄存器集、内核栈、TEB、用户栈)
线程共享的资源(同进程内):
- 虚拟地址空间
- 系统资源
1.3 抢占式多任务
Windows 支持抢占式多任务处理,多个进程中的多个线程可以”同时”执行。
- 单处理器:通过时间片轮转实现”伪并行”
- 多处理器:真正并行执行,最多可同时运行与处理器数量相同的线程
二、进程创建
2.1 CreateProcess 函数
CreateProcess 函数用于创建独立运行的新进程,形成父子关系。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
void _tmain(int argc, TCHAR *argv[])
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (argc != 2)
{
printf("Usage: %s [cmdline]\n", argv[0]);
return;
}
// 启动子进程
if (!CreateProcess(
NULL, // 不指定模块名(使用命令行)
argv[1], // 命令行
NULL, // 进程句柄不可继承
NULL, // 线程句柄不可继承
FALSE, // 句柄继承设为 FALSE
0, // 无创建标志
NULL, // 使用父进程环境块
NULL, // 使用父进程起始目录
&si, // STARTUPINFO 结构指针
&pi)) // PROCESS_INFORMATION 结构指针
{
printf("CreateProcess failed (%d).\n", GetLastError());
return;
}
// 等待子进程退出
WaitForSingleObject(pi.hProcess, INFINITE);
// 关闭进程和线程句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}2.2 返回信息
CreateProcess 成功时返回 PROCESS_INFORMATION 结构:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; // 进程句柄
HANDLE hThread; // 主线程句柄
DWORD dwProcessId; // 进程 ID
DWORD dwThreadId; // 线程 ID
} PROCESS_INFORMATION;⚠️ 句柄具有完全访问权限,使用完毕后必须用 CloseHandle 关闭。
2.3 其他创建函数
| 函数 | 用途 |
|---|---|
| CreateProcessAsUser | 指定用户安全上下文创建进程 |
| CreateProcessWithLogonW | 使用指定用户凭据创建进程 |
三、线程创建
3.1 CreateThread 函数
CreateThread 函数为进程创建新线程,需指定线程函数的起始地址。
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#define MAX_THREADS 3
#define BUF_SIZE 255
DWORD WINAPI MyThreadFunction(LPVOID lpParam);
// 线程数据结构
typedef struct MyData {
int val1;
int val2;
} MYDATA, *PMYDATA;
int _tmain()
{
PMYDATA pDataArray[MAX_THREADS];
DWORD dwThreadIdArray[MAX_THREADS];
HANDLE hThreadArray[MAX_THREADS];
// 创建多个工作线程
for (int i = 0; i < MAX_THREADS; i++)
{
// 分配线程数据内存
pDataArray[i] = (PMYDATA)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(MYDATA));
if (pDataArray[i] == NULL)
ExitProcess(2);
// 设置线程参数
pDataArray[i]->val1 = i;
pDataArray[i]->val2 = i + 100;
// 创建线程
hThreadArray[i] = CreateThread(
NULL, // 默认安全属性
0, // 默认栈大小
MyThreadFunction, // 线程函数
pDataArray[i], // 线程参数
0, // 默认创建标志
&dwThreadIdArray[i]); // 返回线程 ID
if (hThreadArray[i] == NULL)
{
ErrorHandler(TEXT("CreateThread"));
ExitProcess(3);
}
}
// 等待所有线程结束
WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);
// 关闭句柄并释放内存
for (int i = 0; i < MAX_THREADS; i++)
{
CloseHandle(hThreadArray[i]);
if (pDataArray[i] != NULL)
{
HeapFree(GetProcessHeap(), 0, pDataArray[i]);
pDataArray[i] = NULL;
}
}
return 0;
}
DWORD WINAPI MyThreadFunction(LPVOID lpParam)
{
PMYDATA pDataArray = (PMYDATA)lpParam;
// 使用线程安全函数输出
TCHAR msgBuf[BUF_SIZE];
StringCchPrintf(msgBuf, BUF_SIZE,
TEXT("Parameters = %d, %d\n"),
pDataArray->val1, pDataArray->val2);
// ... 线程工作逻辑 ...
return 0;
}3.2 线程函数签名
线程函数必须遵循 ThreadProc 签名:
DWORD WINAPI ThreadProc(LPVOID lpParameter);3.3 重要注意事项
⚠️ 使用 CRT 时的替代方案:如果线程函数中使用 C 运行时库(CRT),应使用
_beginthreadex代替CreateThread,因为许多 CRT 函数不是线程安全的。
⚠️ 避免传递局部变量地址:如果创建线程在新线程执行前退出,传递的局部变量指针将变为无效。应使用:
- 动态分配的内存
- 全局变量(需同步)
- 或让创建线程等待新线程终止
3.4 CreateThread 参数详解
| 参数 | 说明 |
|---|---|
| 安全属性 | 包含继承标志和安全描述符 |
| 栈大小 | 初始栈大小,系统按需增长 |
| 创建标志 | 可创建挂起状态的线程 |
| 线程 ID | 返回的唯一标识符 |
四、多线程同步
4.1 为什么需要同步?
多线程访问共享资源时,必须同步以避免:
- 竞态条件:多个线程同时修改同一数据
- 死锁:线程相互等待对方释放资源
4.2 同步对象
Windows 提供多种同步对象,其状态为已发出信号或未发出信号:
| 对象 | 触发信号条件 |
|---|---|
| 控制台输入缓冲区 | 有未读输入(击键/鼠标) |
| 事件 | 手动/自动设置 |
| 互斥体(Mutex) | 释放所有权时 |
| 进程 | 进程终止时 |
| 信号量 | 计数归零时 |
| 线程 | 线程终止时 |
| 计时器 | 到期时 |
4.3 等待函数
// 等待单个对象
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
// 等待多个对象
DWORD WaitForMultipleObjects(DWORD nCount,
const HANDLE *lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds);4.4 互斥体使用示例
HANDLE hMutex;
// 访问共享资源前
WaitForSingleObject(hMutex, INFINITE);
// 临界区 - 访问共享资源
// ...
// 访问完成后释放
ReleaseMutex(hMutex);4.5 关键段(Critical Section)
对于单个进程内的线程同步,关键段比互斥体更高效:
CRITICAL_SECTION cs;
// 初始化
InitializeCriticalSection(&cs);
// 进入临界区
EnterCriticalSection(&cs);
// 临界区代码...
// 离开临界区
LeaveCriticalSection(&cs);
// 清理
DeleteCriticalSection(&cs);| 函数 | 说明 |
|---|---|
| EnterCriticalSection | 请求所有权,已占用则阻塞 |
| TryEnterCriticalSection | 非阻塞请求所有权 |
| LeaveCriticalSection | 释放所有权 |
五、高级概念
5.1 作业对象(Job Object)
作业对象允许将进程组作为一个单元管理:
- 可控制关联进程的属性
- 操作作用于所有关联进程
- 是可命名、安全、可共享的对象
典型用途:限制进程组的资源使用(CPU、内存等)。
5.2 线程池(Thread Pool)
线程池是工作线程的集合,用于高效执行异步回调:
优势:
- 减少应用程序线程数量
- 自动管理工作线程
- 支持工作项排队、定时器、I/O 绑定
5.3 光纤(Fiber)
光纤是应用程序手动调度的执行单元:
- 在调度它的线程上下文中运行
- 每个线程可调度多个光纤
- 适用于移植自调度线程的应用程序
💡 与设计良好的多线程应用相比,光纤通常没有优势。
5.4 用户模式调度(UMS)
UMS 是一种轻量级机制,应用程序可用于自行调度线程:
- 在用户态切换线程,无需涉及系统调度器
- 内核阻塞 UMS 线程时重新获得处理器控制
- 每个 UMS 线程有独立的线程上下文
- 比线程池更高效,适合少量系统调用的短时工作项
六、进程 vs 线程对比
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源隔离 | 独立地址空间 | 共享地址空间 |
| 创建开销 | 较大 | 较小 |
| 通信方式 | IPC(管道、共享内存等) | 直接读写共享变量 |
| 稳定性 | 一个进程崩溃不影响其他 | 一个线程崩溃可能导致整个进程终止 |
| 切换开销 | 需要切换地址空间 | 仅需保存/恢复寄存器 |
| 适用场景 | 需要隔离性的任务 | 并行计算、I/O 密集型任务 |
七、相关 API 参考
进程相关
| 函数 | 功能 |
|---|---|
| CreateProcess | 创建新进程 |
| OpenProcess | 打开现有进程 |
| TerminateProcess | 终止进程 |
| GetExitCodeProcess | 获取进程退出码 |
| WaitForSingleObject | 等待进程结束 |
线程相关
| 函数 | 功能 |
|---|---|
| CreateThread | 创建新线程 |
| CreateRemoteThread | 在其他进程中创建线程 |
| OpenThread | 打开现有线程 |
| SuspendThread | 挂起线程 |
| ResumeThread | 恢复线程 |
| TerminateThread | 终止线程 |
| GetExitCodeThread | 获取线程退出码 |