C++: 多线程同步
最后更新:2026-08-26
第43课我们学了多线程基础。
现在,我们要深入多线程同步——让多个线程协调工作,避免竞争和死锁。
多线程编程的难点不在于创建线程,而在于同步。
1. 死锁
(1) 1.1 什么是死锁?
死锁(Deadlock)是指两个或多个线程互相等待对方释放资源,导致谁也无法继续。
四个必要条件:
- 互斥:资源不能共享
- 持有并等待:持有资源的线程等待其他资源
- 不可抢占:资源不能被强制释放
- 循环等待:存在线程的循环等待链
(2) 1.2 死锁示例
示例:死锁(难度⭐⭐⭐)
▶ 示例 2:多线程编程演示(难度⭐)
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void thread1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock2(mtx2); // 等待mtx2
std::cout << "Thread 1 done" << std::endl;
}
void thread2() {
std::lock_guardstd::mutex lock2(mtx2);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guardstd::mutex lock1(mtx1); // 等待mtx1
std::cout << "Thread 2 done" << std::endl;
}
int main() {
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
return 0;
}
输出:
Thread 1 done
Thread 2 done
💡 提示:
- 这个程序可能永远运行不下去(死锁)
(3) 1.3 避免死锁
方法1:固定加锁顺序
void thread1() {
std::lock_guardstd::mutex lock1(mtx1);
std::lock_guardstd::mutex lock2(mtx2); // 总是先mtx1,再mtx2
std::cout << "Thread 1 done" << std::endl;
}
void thread2() {
std::lock_guardstd::mutex lock1(mtx1); // 和thread1一样的顺序
std::lock_guardstd::mutex lock2(mtx2);
std::cout << "Thread 2 done" << std::endl;
}
方法2:用std::lock同时锁多个互斥量
void safe_function() {
std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock);
std::lock(lock1, lock2); // 同时加锁,避免死锁
// ...
}
方法3:用std::scoped_lock(C++17,推荐)
void safe_function() {
std::scoped_lock lock(mtx1, mtx2); // 自动避免死锁
// ...
}
2. 读写锁
(1) 2.1 为什么需要读写锁?
问题: 互斥量太"保守"——即使多个线程只是读取数据,也要排队。
解决方案: 读写锁(std::shared_mutex,C++17)
| 锁类型 | 功能 |
|---|---|
| 独占锁 | 写操作,其他线程不能读写 |
| 共享锁 | 读操作,多个线程可以同时读 |
(2) 2.2 示例:用读写锁保护缓存(难度⭐⭐⭐)
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <unordered_map>
std::unordered_map<int, int> cache;
std::shared_mutex smtx;
int get(int key) {
std::shared_lock<std::shared_mutex> lock(smtx); // 共享锁(读)
auto it = cache.find(key);
if (it != cache.end()) {
return it->second;
}
return -1;
}
void set(int key, int value) {
std::unique_lock<std::shared_mutex> lock(smtx); // 独占锁(写)
cache[key] = value;
}
int main() {
// 多个线程可以同时读,写的时候排他
return 0;
}
输出:
(程序输出)
3. 原子操作
(1) 3.1 什么是原子操作?
原子操作(Atomic Operation)是不可分割的操作——要么全部执行,要么都不执行。
优势: 无需加锁,性能高。
(2) 3.2 std::atomic
C++11提供了 std::atomic 模板。
示例:原子计数器(难度⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
std::atomic<int> counter(0); // 原子变量
void increment() {
for (int i = 0; i < 1000; i++) {
counter++; // 原子操作,无需加锁
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // 一定是10000
return 0;
}
(3) 3.3 内存顺序
std::atomic 支持内存顺序(Memory Order),用于控制同步范围。
常用内存顺序:
std::memory_order_relaxed:最弱,只保证原子性std::memory_order_acquire:读操作,防止重排std::memory_order_release:写操作,防止重排std::memory_order_seq_cst:最强,默认(顺序一致性)
4. 线程池
(1) 4.1 为什么需要线程池?
问题: 频繁创建/销毁线程开销大。
解决方案: 线程池——预先创建一组线程,复用它们。
(2) 4.2 简单线程池实现
示例:线程池(难度⭐⭐⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
class ThreadPool {
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex mtx;
std::condition_variable cv;
bool stop;
public:
ThreadPool(size_t numThreads) : stop(false) {
for (size_t i = 0; i < numThreads; i++) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this] {
return stop || !tasks.empty();
});
if (stop && tasks.empty()) {
return;
}
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
std::lock_guard<std::mutex> lock(mtx);
stop = true;
}
cv.notify_all();
for (auto& t : workers) {
t.join();
}
}
template<class F>
void enqueue(F&& f) {
{
std::lock_guardstd::mutex lock(mtx);
tasks.emplace(std::forward<F>(f));
}
cv.notify_one();
}
};
int main() {
ThreadPool pool(4); // 创建4个线程
for (int i = 0; i < 10; i++) {
pool.enqueue([i] {
std::cout << "Task " << i << " running" << std::endl;
});
}
return 0;
}
5. 实战:并行排序
▶ 示例 1:用多线程加速排序(难度⭐⭐⭐⭐)
#include <iostream>
#include <vector>
#include <thread>
#include <algorithm>
void parallelSort(std::vector<int>& v, int left, int right) {
if (left >= right) return;
std::sort(v.begin() + left, v.begin() + right + 1);
}
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// 用2个线程并行排序
std::thread t1(parallelSort, std::ref(v), 0, v.size()/2);
std::thread t2(parallelSort, std::ref(v), v.size()/2 + 1, v.size()-1);
t1.join();
t2.join();
// 合并两个有序部分
std::inplace_merge(v.begin(), v.begin() + v.size()/2 + 1, v.end());
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
输出:
3 1 4 1 5 9 2 6
▶ 示例 3:用 mutex 保护共享计数器(难度⭐)
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 1000; i++) {
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "counter = " << counter << std::endl;
return 0;
}
输出:
counter = 10000
❓ 常见问题
std::cout 打印日志(但要加互斥量) - 用Valgrind、Helgrind等工具检测数据竞争 - 用Thread Sanitizer(GCC/Clang)std::jthread(自动join) - 信号量(std::counting_semaphore) - 闩锁(std::latch) - 屏障(std::barrier)📖 小节
| 知识点 | 要点 |
|---|---|
| 死锁 | 四个条件,三种避免方法 |
| 读写锁 | std::shared_mutex(C++17) |
| 原子操作 | std::atomic,无需加锁 |
| 线程池 | 复用线程,减少开销 |
| 并行算法 | C++17 std::execution |
📝 作业
-
**基础题 (Difficulty ⭐):创建两个线程同时对一个全局变量做 100000 次自增,观察结果是否正确。然后用 std::mutex 保护后重新测试。
-
**进阶题 (Difficulty ⭐⭐):用 std::lock_guard 保护一个"账户余额"变量,模拟两个线程同时存款/取款操作,确保最终余额正确。
-
**挑战题 (Difficulty ⭐⭐⭐):用 std::condition_variable 实现一个"生产者-消费者"模型。一个线程生产数据放入队列,另一个线程消费数据。队列为空时消费者等待。
- 竞态条件:多个线程同时访问共享数据
- std::mutex 互斥锁保护临界区
- std::lock_guard 自动加锁解锁(RAII)
- 死锁:两个线程互相等待对方释放锁
- 条件变量 std::condition_variable 通知线程
Phase 6 结束!下一步:Phase 7(进阶与实战)