C++ 多线程同步机制详解:互斥锁、条件变量与原子操作
互斥锁 (Mutex)
核心概念
互斥锁是最基本的同步机制,用于保护共享资源,防止多个线程同时访问同一资源导致的数据竞争问题。
工作原理
- 线程访问共享资源前尝试获取锁
- 如果锁可用,线程获得锁并访问资源
- 如果锁不可用,线程阻塞等待
- 完成访问后释放锁,其他线程可以获取
C++ 实现
#include std::mutex mtx; // 声明互斥锁void critical_section() { mtx.lock(); // 获取锁 // 访问共享资源 mtx.unlock(); // 释放锁}
RAII 包装器(推荐)
void safe_critical_section() { std::lock_guard<std::mutex> lock(mtx); // 构造时自动加锁 // 访问共享资源 // 析构时自动解锁}
高级用法:std::unique_lock
void flexible_critical_section() { std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟加锁 // ... 非临界区操作 ... lock.lock(); // 手动加锁 // 访问共享资源 lock.unlock(); // 可提前解锁 // ... 其他非临界区操作 ... // 离开作用域时,如果仍持有锁,自动解锁}
特点
- 阻塞式同步:获取不到锁时线程会阻塞
- 非递归:同一线程重复加锁会导致死锁
- 不可拷贝/移动:保证锁的所有权明确
条件变量 (Condition Variable)
核心概念
条件变量用于线程间的通信,允许线程在特定条件成立前阻塞等待,条件成立后被唤醒。
工作原理
- 线程获取互斥锁
- 检查条件是否满足
- 如果不满足,调用
wait()
释放锁并阻塞 - 其他线程修改条件后通知等待线程
- 被通知线程重新获取锁并检查条件
C++ 实现
#include #include std::mutex mtx;std::condition_variable cv;bool ready = false;void waiting_thread() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return ready; }); // 等待条件成立 // 条件满足后执行操作}void notifying_thread() { { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); // 通知一个等待线程 // cv.notify_all(); // 通知所有等待线程}
关键点
- 总是与互斥锁配合使用
- 使用谓词避免虚假唤醒
// 正确用法:使用谓词检查条件 cv.wait(lock, []{ return condition; }); // 错误用法:可能因虚假唤醒导致问题 while (!condition) { cv.wait(lock); }
- 通知时机:
notify_one()
:唤醒一个等待线程notify_all()
:唤醒所有等待线程
适用场景
- 生产者-消费者问题
- 线程池任务调度
- 事件驱动型架构
原子操作 (Atomic Operations)
核心概念
原子操作提供无锁同步机制,保证对基本数据类型的操作不可分割,无需显式加锁。
工作原理
- 利用CPU的原子指令实现
- 保证操作的原子性(不可中断)
- 提供内存顺序控制
C++ 实现
#include std::atomic<int> counter(0); // 原子整型void increment() { counter.fetch_add(1, std::memory_order_relaxed); // 原子自增}void decrement() { counter.fetch_sub(1, std::memory_order_relaxed); // 原子自减}
内存顺序
C++ 提供6种内存顺序,控制操作间的可见性和顺序性:
memory_order_seq_cst
memory_order_acquire
memory_order_release
memory_order_acq_rel
memory_order_consume
memory_order_relaxed
常用操作
std::atomic<int> value;// 存储值value.store(42, std::memory_order_release);// 加载值int x = value.load(std::memory_order_acquire);// 交换值int old = value.exchange(100);// 比较交换(CAS)int expected = 100;bool success = value.compare_exchange_strong(expected, 200);
适用场景
- 计数器、标志位等简单共享变量
- 无锁数据结构实现
- 性能敏感的同步场景
三种机制对比
综合示例:线程安全队列
#include #include #include #include template <typename T>class ThreadSafeQueue {public: void push(T value) { { std::lock_guard<std::mutex> lock(mtx); queue.push(std::move(value)); } cv.notify_one(); } bool try_pop(T& value) { std::lock_guard<std::mutex> lock(mtx); if (queue.empty()) return false; value = std::move(queue.front()); queue.pop(); return true; } bool wait_pop(T& value) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [this]{ return !queue.empty() || stopped; }); if (stopped) return false; value = std::move(queue.front()); queue.pop(); return true; } void stop() { { std::lock_guard<std::mutex> lock(mtx); stopped = true; } cv.notify_all(); } size_t size() const { std::lock_guard<std::mutex> lock(mtx); return queue.size(); }private: mutable std::mutex mtx; std::condition_variable cv; std::queue<T> queue; std::atomic<bool> stopped{false};};
使用建议
- 优先考虑原子操作:对于简单计数器、标志位等场景
- 复杂同步使用互斥锁:当需要保护复杂数据结构时
- 条件变量用于等待通知:当线程需要等待特定条件时
- 遵循RAII原则:使用
lock_guard
和unique_lock
管理锁 - 避免嵌套锁:容易导致死锁
- 注意虚假唤醒:条件变量等待总是使用谓词
- 谨慎选择内存顺序:原子操作默认使用
memory_order_seq_cst
,高性能场景可考虑更宽松的顺序