在 C++ 中,同步机制是多线程编程中确保数据一致性和避免竞态条件的核心工具。以下是 C++ 标准库(C++11 及后续版本)提供的同步机制及其实际应用场景的详细解析:
一、互斥量(Mutex)
1. 基础互斥量
#include
std::mutex mtx; // 全局互斥量
void thread_safe_function() {
mtx.lock();
// 临界区操作(访问共享资源)
mtx.unlock(); // 必须手动解锁,否则死锁
}
问题:手动解锁易导致异常安全问题(如临界区抛出异常)
2. RAII 锁管理器
void safe_function() {
std::lock_guard lock(mtx); // 自动加锁/解锁
// 临界区操作
} // 离开作用域自动解锁
优化:使用 std::unique_lock(更灵活,支持延迟锁定和所有权转移)
std::unique_lock lock(mtx, std::defer_lock);
lock.lock(); // 显式锁定
lock.unlock(); // 可中途释放锁
二、条件变量(Condition Variable)
用于线程间通信,典型生产者-消费者模型:
#include
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
// 生产者
void producer() {
std::lock_guard lock(mtx);
// 生产数据
data_ready = true;
cv.notify_one(); // 通知消费者
}
// 消费者
void consumer() {
std::unique_lock lock(mtx);
cv.wait(lock, []{ return data_ready; }); // 自动释放锁并等待
// 消费数据
data_ready = false;
}
关键点:
- wait() 会释放锁并阻塞,直到被唤醒后重新获取锁
- 使用 谓词(Predicate) 防止虚假唤醒
三、原子操作(Atomic)
无锁编程的核心工具:
#include
std::atomic counter(0); // 原子计数器
void increment() {
counter.fetch_add(1, std::memory_order_relaxed);
}
bool try_update(int expected) {
return counter.compare_exchange_weak(
expected,
expected + 1,
std::memory_order_acq_rel,
std::memory_order_acquire
);
}
内存序选项:
- memory_order_relaxed:无顺序保证(计数器场景)
- memory_order_acquire/release:同步特定内存访问
- memory_order_seq_cst:默认严格顺序(性能最差)
四、读写锁(Shared Mutex)
C++14 引入,适用于读多写少场景:
#include
std::shared_mutex rw_mutex;
void read_data() {
std::shared_lock lock(rw_mutex);
// 多个线程可同时读取
}
void write_data() {
std::unique_lock lock(rw_mutex);
// 独占写入
}
五、信号量(Semaphore)
C++20 新增,控制资源访问数量:
#include
std::counting_semaphore<10> sem(3); // 允许3个线程同时访问
void limited_resource_access() {
sem.acquire();
// 使用受限资源
sem.release();
}
六、闩(Latch)与屏障(Barrier)
C++20 引入,用于多阶段并行任务:
#include
#include
std::latch completion_latch(5); // 等待5个线程
std::barrier sync_barrier(5); // 可重复使用的屏障
void worker() {
// 阶段1
completion_latch.count_down();
completion_latch.wait(); // 等待所有线程完成阶段1
// 阶段2
sync_barrier.arrive_and_wait(); // 同步点
}
七、同步机制选择策略
场景 | 工具选择 | 注意事项 |
简单互斥 | std::mutex + lock_guard | 避免嵌套锁 |
复杂锁管理 | std::unique_lock | 支持超时和条件变量 |
高频计数器 | std::atomic | 选择合适的内存序 |
读写分离 | std::shared_mutex | 防止写饥饿 |
资源池控制 | std::counting_semaphore | 初始值设置合理 |
任务分阶段 | std::latch/std::barrier | 不可复制需引用传递 |
八、实际应用陷阱与解决方案
1、死锁预防:
使用 std::scoped_lock 解决多锁顺序问题
std::mutex mtx1, mtx2;
std::scoped_lock lock(mtx1, mtx2); // 自动死锁避免
2、优先级反转:
使用优先级继承协议(需操作系统支持)
或改用无锁数据结构
3、虚假唤醒:
始终在条件变量等待循环中使用谓词
cv.wait(lock, [&]{ return !queue.empty(); });
4、锁粒度优化:
细粒度锁(如并发哈希表分桶加锁)
无锁数据结构(如 boost::lockfree::queue)
九、性能优化技巧
1、锁竞争分析:
使用 valgrind --tool=drd 检测锁争用
2、无锁化改造:
使用原子操作 + CAS(Compare-And-Swap)
std::atomic head;
void push(int value) {
Node* new_node = new Node{value};
new_node->next = head.load(std::memory_order_relaxed);
while(!head.compare_exchange_weak(
new_node->next,
new_node,
std::memory_order_release,
std::memory_order_relaxed
));
}
3、线程局部存储:
thread_local int local_counter = 0; // 每个线程独立副本
十、同步机制演进趋势
1、并行算法(C++17+):
#include
std::for_each(std::execution::par, vec.begin(), vec.end(), [](auto& x){
// 并行处理
});
2、协程同步(C++20):
std::future async_op() {
co_await std::suspend_always{};
co_return 42;
}
通过合理选择同步机制并遵循以下原则,可构建高性能线程安全系统:
- 最小化临界区:减少锁持有时间
- 锁分级策略:定义锁的获取顺序
- 无锁优先:对高频访问数据使用原子操作
- 防御性编程:假设所有共享数据都需要保护
- 工具验证:使用 ThreadSanitizer 检测数据竞争