进程和线程区别
| 特性 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 定义 | 程序在操作系统中的一次执行实例 | 进程内的一个执行单元,是 CPU 调度的基本单位 |
| 资源 | 拥有独立的内存空间、文件句柄等系统资源 | 共享所属进程的资源,仅拥有自己的栈和寄存器 |
| 创建开销 | 高(需分配完整内存和系统资源) | 低(仅需创建线程栈和上下文) |
| 通信方式 | 通过 IPC(管道、消息队列、共享内存等) | 直接共享进程内存(需同步机制) |
| 调度 | 传统 OS 中是调度单位,现代 OS 中通常是线程 | 现代 OS 中的基本调度单位 |
| 并发性 | 进程间可并发执行(多核并行) | 同一进程的线程可并发执行(多核并行) |
| 崩溃影响 | 一个进程崩溃不影响其他进程 | 同一进程的某个线程崩溃可能导致整个进程崩溃 |
| 上下文切换开销 | 高(需切换内存映射、寄存器等) | 低(仅需切换线程栈和寄存器) |
| 编程难度 | 高(IPC 复杂,资源管理繁琐) | 中等(需处理同步和竞态条件) |
常用函数
| 功能 | Windows API | POSIX Threads | C++11 标准库 |
|---|---|---|---|
| 创建线程 | CreateThread | pthread_create | std::thread |
| 等待线程结束 | WaitForSingleObject | pthread_join | std::thread::join |
| 线程休眠 | Sleep | sleep/usleep | std::this_thread::sleep |
| 互斥锁 | CRITICAL_SECTION | pthread_mutex_* | std::mutex |
| 条件变量 | CreateEvent | pthread_cond_* | std::condition_variable |
| 原子操作 | InterlockedIncrement | __sync_fetch_and_add | std::atomic |
| 线程局部存储 | TlsAlloc | pthread_key_create | thread_local |
常用示例
以下是为各线程 API 示例添加的详细注释,解释代码逻辑和关键知识点:
一、线程创建与管理
1. Windows API
#include <windows.h> // Windows API头文件
#include <iostream>
// 线程函数原型:必须返回DWORD,接受LPVOID参数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
// 将参数转换为int类型
int threadId = *(int*)lpParam;
std::cout << "线程 " << threadId << " 正在运行" << std::endl;
// 线程休眠1秒,模拟耗时操作
Sleep(1000); // 单位:毫秒
std::cout << "线程 " << threadId << " 结束" << std::endl;
return 0; // 返回值可通过GetExitCodeThread获取
}
int main() {
int id = 1; // 传递给线程的参数
// 创建线程
HANDLE hThread = CreateThread(
NULL, // 安全属性,NULL表示默认
0, // 线程栈大小,0表示默认
ThreadFunction, // 线程函数地址
&id, // 传递给线程函数的参数
0, // 创建标志(如CREATE_SUSPENDED可挂起线程)
NULL // 输出线程ID,NULL表示不获取
);
if (hThread == NULL) {
std::cerr << "创建线程失败,错误码: " << GetLastError() << std::endl;
return 1;
}
// 等待线程结束(INFINITE表示无限等待)
WaitForSingleObject(hThread, INFINITE);
// 关闭线程句柄(减少内核对象引用计数)
CloseHandle(hThread);
std::cout << "主线程结束" << std::endl;
return 0;
}
二、线程同步机制
线程同步机制有多种方式可以实现,也有多套api函数可以用,比如Windows 使用CRITICAL_SECTION结构实现轻量级互斥;POSIX 标准使用pthread_mutex_t结构实现互斥锁;C++11 引入了跨平台的线程库,使用std::mutex类以及lock、unlock等函数来实现,本文主要讲Windows api函数。
1. 互斥锁(Mutex)
实现这样一个功能:创建四个线程,每个线程对变量sharedCounter执行1000000次+1,预期结果是4000000。可以预想到,如果没有互斥锁,会出现这种情况:线程1和线程2同时对100进行+1操作,最后写入101,(实际应该是102),最终结果会比预期结果少。
使用互斥锁的思路是:定义一个临界区,这个临界区的意义就相当于使用权,只有拿到使用权才能对sharedCounter执行+1操作,执行一次之后解锁,每次只允许一个线程进入临界区。这样就不会出现同时进行+1导致少加的情况出现。
加锁版:
#include <windows.h>
#include <iostream>
#include <vector>
CRITICAL_SECTION cs; // 声明临界区
int sharedCounter = 0; // 共享资源
DWORD WINAPI Worker(LPVOID iterations) {
int iter = *(int*)iterations;
for (int i = 0; i < iter; ++i) {
EnterCriticalSection(&cs); // 进入临界区(加锁)
++sharedCounter; // 关键操作
LeaveCriticalSection(&cs); // 离开临界区(解锁)
}
return 0;
}
int main() {
const int numThreads = 4;
const int iterations = 1000000;
std::vector<HANDLE> threads;
InitializeCriticalSection(&cs); // 初始化临界区
// 创建多个线程
for (int i = 0; i < numThreads; ++i) {
int* arg = new int(iterations); // 为每个线程创建独立参数
threads.push_back(CreateThread(
NULL, 0, Worker, arg, 0, NULL
));
}
// 等待所有线程完成
WaitForMultipleObjects(numThreads, threads.data(), TRUE, INFINITE);
// 清理资源
for (auto h : threads) CloseHandle(h);
DeleteCriticalSection(&cs); // 销毁临界区
std::cout << "正确结果: " << sharedCounter
<< " (预期: " << numThreads * iterations << ")" << std::endl;
return 0;
}
结果:

无锁版:
#include <windows.h>
#include <iostream>
#include <vector>
int sharedCounter = 0; // 共享资源,无任何同步保护
DWORD WINAPI Worker(LPVOID iterations) {
int iter = *(int*)iterations;
for (int i = 0; i < iter; ++i) {
// 非原子操作,存在竞态条件
++sharedCounter; // 等价于:read-modify-write 三个步骤
}
return 0;
}
int main() {
int numThreads = 4;
int iterations = 1000000;
std::vector<HANDLE> threads;
// 创建多个线程同时修改共享资源
for (int i = 0; i < numThreads; ++i) {
threads.push_back(CreateThread(
NULL, 0, Worker, &iterations, 0, NULL
));
}
// 等待所有线程完成
WaitForMultipleObjects(numThreads, threads.data(), TRUE, INFINITE);
// 关闭线程句柄
for (auto h : threads) {
CloseHandle(h);
}
// 理论结果应为 numThreads * iterations = 4000000
// 但实际结果通常小于预期
std::cout << "无锁保护的结果: " << sharedCounter
<< " (预期: " << numThreads * iterations << ")" << std::endl;
return 0;
}
结果:

互斥体
互斥体原理跟锁差不多,区别在于:
互斥体(Mutex):是内核对象,可跨进程使用(如多个程序访问同一资源),性能开销较大。
临界区(Critical Section):是用户模式对象,只能在同一进程内使用,性能更优。
#include <windows.h>
#include <iostream>
#include <vector>
// 共享资源:计数器
int g_counter = 0;
// 互斥体句柄
HANDLE g_hMutex = NULL;
// 线程函数:增加计数器
DWORD WINAPI ThreadFunc(LPVOID lpParam) {
// 传入参数:线程编号
int threadId = *(int*)lpParam;
// 循环10次,每次尝试获取互斥体并修改计数器
for (int i = 0; i < 100000; i++) {
// 等待互斥体(获取锁),无限等待
WaitForSingleObject(g_hMutex, INFINITE);
// 安全访问共享资源
int temp = g_counter;
g_counter = temp + 1;
// 释放互斥体(释放锁)
ReleaseMutex(g_hMutex);
}
return 0;
}
int main() {
const int THREAD_COUNT = 4;
HANDLE hThreads[THREAD_COUNT];
int threadIds[THREAD_COUNT] = { 1, 2 };
// 创建互斥体(非初始拥有者,匿名互斥体)
g_hMutex = CreateMutex(NULL, FALSE, NULL);
if (g_hMutex == NULL) {
std::cout << "创建互斥体失败,错误码: " << GetLastError() << std::endl;
return 1;
}
// 创建多个线程
for (int i = 0; i < THREAD_COUNT; i++) {
hThreads[i] = CreateThread(
NULL, // 安全属性
0, // 栈大小
ThreadFunc, // 线程函数
&threadIds[i], // 传入线程编号
0, // 创建标志
NULL // 线程ID
);
if (hThreads[i] == NULL) {
std::cout << "创建线程失败,错误码: " << GetLastError() << std::endl;
// 关闭已创建的线程句柄
for (int j = 0; j < i; j++) {
CloseHandle(hThreads[j]);
}
// 关闭互斥体句柄
CloseHandle(g_hMutex);
return 1;
}
}
// 等待所有线程结束
WaitForMultipleObjects(THREAD_COUNT, hThreads, TRUE, INFINITE);
// 关闭线程句柄
for (int i = 0; i < THREAD_COUNT; i++) {
CloseHandle(hThreads[i]);
}
// 关闭互斥体句柄
CloseHandle(g_hMutex);
std::cout << "最终计数器值: " << g_counter << std::endl;
return 0;
}
如需测试反例,删去WaitForSingleObject(g_hMutex, INFINITE)和ReleaseMutex(g_hMutex)即可。
信号量
信号量跟互斥体差不多,只不过互斥体是内核提供,信号量是自定义,下述代码实现了限制游戏多开:
#include <windows.h>
#include <iostream>
int main() {
// 创建命名信号量,初始计数为1,最大计数为1
HANDLE hSemaphore = CreateSemaphore(
NULL, // 默认安全属性
1, // 初始计数(允许1个实例)
1, // 最大计数
L"Global\\MyGameSemaphore" // 信号量名称
);
// 检查是否创建失败(已存在)
if (hSemaphore == NULL || GetLastError() == ERROR_ALREADY_EXISTS) {
std::cout << "游戏已在运行,无法多开!" << std::endl;
if (hSemaphore) CloseHandle(hSemaphore);
return 1;
}
// 游戏主逻辑
std::cout << "游戏启动成功!" << std::endl;
std::cin.get(); // 等待用户输入,模拟游戏运行
// 释放信号量并关闭句柄
ReleaseSemaphore(hSemaphore, 1, NULL);
CloseHandle(hSemaphore);
return 0;
}
效果:

其他
除此之外,还有其他对象,不一一赘述,见表格:
| 同步对象 | Windows 实现 | Linux/POSIX 实现 | 核心特性 | 适用场景 | 典型用法 |
|---|---|---|---|---|---|
| 互斥锁 (Mutex) | CreateMutex/WaitForSingleObject | pthread_mutex_init/pthread_mutex_lock | – 确保同一时间只有一个线程访问资源 – 支持所有权(同一线程可多次加锁) – 可避免死锁(需正确释放) | 保护共享数据(如全局变量、文件句柄) | 读取 / 修改数据库记录、文件写入 |
| 信号量 (Semaphore) | CreateSemaphore/ReleaseSemaphore | sem_open/sem_post | – 控制同时访问资源的线程数量 – 通过计数器(初始值 / 最大值)实现限流 – 适用于有限资源管理 | 连接池、线程池、I/O 设备并发控制 | 数据库连接数限制、网络请求并发数控制 |
| 条件变量 (Condition Variable) | CreateConditionVariable/SleepConditionVariableCS | pthread_cond_init/pthread_cond_wait | – 线程等待特定条件满足时被唤醒 – 需配合互斥锁使用 – 解决 “忙等待” 问题,降低 CPU 消耗 | 生产者 – 消费者模型、异步任务通知 | 任务队列处理、网络数据接收通知 |
| 临界区 (Critical Section) | InitializeCriticalSection/EnterCriticalSection | 无(POSIX 用互斥锁替代) | – 轻量级同步机制 – 仅适用于同一进程内线程 – 不可跨进程,效率高于互斥锁 | 进程内快速数据保护(如局部共享变量) | 函数内临时数据同步、小范围资源访问 |
| 读写锁 (Read-Write Lock) | SRWLOCK(Windows 8+) | pthread_rwlock_init | – 允许多个读线程同时访问 – 写线程独占资源 – 优化 “多读少写” 场景的并发性能 | 配置文件读取、缓存数据访问 | 日志系统读取、字典数据并发查询 |
| 屏障 (Barrier) | InitializeThreadBarrier | pthread_barrier_init | – 确保多个线程在指定点同步 – 所有线程到达屏障后才继续执行 – 适用于并行计算同步 | 并行算法(如矩阵计算、图像渲染) | 多线程任务分阶段同步(如 MapReduce) |
| 事件 (Event) | CreateEvent/SetEvent | pthread_event(非标准,常用条件变量替代) | – 线程间通信的信号机制 – 分为手动重置 / 自动重置事件 – 用于通知状态变化 | 异步操作完成通知、线程启动顺序控制 | 网络请求回调通知、后台任务状态反馈 |