本文内容基于微软官方文档整理,主要介绍 Windows 进程与线程的核心概念、API 使用方法及代码示例。

参考链接:进程和线程 - Win32 apps | Microsoft Learn

一、核心概念

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获取线程退出码

参考资料