多线程编程核心概念:线程同步、异步、死锁、线程池
一、什么是多线程
多线程是计算机科学中的一个核心概念,它允许一个进程内同时运行多个执行流,每个执行流称为一个线程。这些线程共享进程的资源(如内存、文件句柄),但各自拥有独立的栈和寄存器状态,能够并行(在多核CPU上)或并发(在单核CPU上通过时间片轮转)地执行任务。
线程(Thread)
线程是操作系统调度的最小执行单位。
- 通信简单(直接共享内存)。线程之间共享:全局变量、堆内存
- 一个进程有多个线程。
进程(你的程序)
├── 线程1(主线程)
├── 线程2(采集线程)
├── 线程3(处理线程)
进程(Process)
进程指一个正在运行的程序。每个进程有独立内存空间、独立资源(文件句柄、句柄表等),但互相不能直接访问。
进程通信(IPC),常见方式:
- 管道(pipe)
- 共享内存
- socket
- 消息队列
单核 CPU
同一时刻只能执行一个线程。采用的是时间片轮转(操作系统调度)
时间线:
线程A → 线程B → 线程C → 线程A → ...
多核 CPU
每个核心可以执行一个线程。采用的是并行执行。
CPU 4核:
核心1 → 线程A
核心2 → 线程B
核心3 → 线程C
核心4 → 线程D
多线程可能带来的问题
一个进程里线程间共享:内存(全局变量、堆)
这就带来两个核心问题:
- 并发访问冲突(数据竞争)
- 执行顺序不可控
为什么多线程不一定更快?
核心原因1:线程切换有开销(上下文切换)
CPU在多个线程之间切换时,需要:保存当前线程状态(寄存器、栈)、恢复下一个线程状态。如果线程很多,大量时间花在“切换线程”,而不是“执行任务”
核心原因2:锁竞争(最常见性能杀手)
std::mutex mtx;
void func() {
std::lock_guard<std::mutex> lock(mtx);
// 操作共享数据
}
多个线程同时执行:
线程A:拿锁 ✔
线程B:等待 ❌
线程C:等待 ❌
线程数越多 → 抢锁越激烈 → 性能越差
本质原因:多线程变成,排队执行(串行化)
核心原因3:CPU核心数限制
如果线程数 > CPU核心数,例如:4核 CPU,开10个线程,会变成:一部分线程执行;一部分线程等待;不停切换
同步(Synchronous)
顺序执行,调用者必须等结果
异步(Asynchronous)
调用后立即返回,结果以后再拿
并发 vs 并行
- 在单核CPU上,多线程通过操作系统快速切换时间片实现并发,微观上依次执行,宏观上看起来“同时”运行。
- 在多核CPU上,多个线程可以真正同时运行,实现并行,提升计算效率。
线程调度
操作系统内核负责线程的调度,决定哪个线程获得CPU时间。调度策略包括抢占式(时间片用完强制切换)和协作式(线程主动让出CPU)。
三、线程同步
多线程最核心问题:多个线程同时操作同一数据
为什么需要锁?
例子:
int count = 0;
void add() {
count++; // 不是原子操作
}
实际是三步,会出现数据竞争问题。多个线程同时读写共享数据时,可能产生不可预期的结果。两个线程同时给一个全局变量加1,最终结果可能比预期少:
1. 读
2. 加
3. 写
解决方案:使用互斥锁(mutex)、原子操作、读写锁等同步机制。
互斥锁(Mutex)
std::mutex mtx;
void func() {
mtx.lock();
// 临界区
mtx.unlock();
}
改进写法(推荐):
std::lock_guard<std::mutex> lock(mtx);
原子操作(Atomic)
不用锁的轻量方案
std::atomic<int> count(0);
count++;
优点:
- 无锁(快)
- 线程安全
缺点:
- 只能做简单操作
条件变量(Condition Variable)
用于线程等待/通知
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
void worker() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
}
用途:
- 生产者-消费者模型
- 阻塞等待事件
四、死锁
什么是死锁
线程互相等待对方持有的资源,导致所有相关线程永远阻塞。
线程A:锁A → 等锁B
线程B:锁B → 等锁A
预防:按固定顺序加锁,使用RAII管理锁,避免嵌套锁等。
死锁的四个条件
- 互斥
- 占有并等待
- 不可剥夺
- 循环等待
如何避免死锁
方法1:固定加锁顺序
// 所有地方都先锁 A 再锁 B
方法2:使用 std::lock
std::lock(mtx1, mtx2);
方法3:减少锁粒度
👉 锁越多越容易死锁
方法4:避免嵌套锁
五、线程池
为什么要线程池?
创建线程很贵:
std::thread t(...); // 系统调用 + 栈分配
频繁创建/销毁 → 性能差
线程池原理
核心结构:
任务队列 + 工作线程
流程:
- 提交任务
- 放入队列
- 空闲线程取任务执行
简化模型
任务队列
↓
线程1 线程2 线程3
线程池优点
- 复用线程(高性能)
- 控制并发数量
- 防止线程爆炸
常见实现
C++标准库
auto f = std::async(std::launch::async, func);
工业级(推荐)
-
Qt:
QThreadPool -
C++:
- 自己实现
- 或用第三方(boost/asio)
六、常见多线程模型
生产者-消费者模型
采集线程 → 队列 → 处理线程
主线程 + 工作线程
UI线程(Qt)
↓
后台线程处理任务
避免UI卡死

浙公网安备 33010602011771号