C++ 并发编程:互斥锁、条件变量与原子操作
线程创建与 join/detach 之外的另一半问题:共享数据怎么同步。覆盖数据竞争、互斥锁的三种用法、条件变量生产者-消费者、原子操作与死锁。 核心概念速查 概念 说明 关键点 互斥锁 同一时刻只允许一个线程进入 std::mutex lock_guard RAII 自动加解锁 推荐,防遗漏 unique_lock 更灵活的 RAII 锁 可手动解锁、可条件变量 条件变量 线程间的等待/通知 std::condition_variable 原子操作 无锁的线程安全 std::atomic 数据竞争 多线程同时访问共享数据 未定义行为,必须防 为什么需要锁 // 无锁: 两个线程同时对 counter++ 可能丢失更新 // counter++ 实际是"读-改-写"三步,可能交错 int counter = 0; void add() { for (int i = 0; i < 100000; ++i) ++counter; // 数据竞争! } 互斥锁三种用法 #include <mutex> std::mutex g_mutex; // 方式1: 原始 lock/unlock(容易忘解锁,不推荐) void f1() { g_mutex.lock(); // ... 临界区 g_mutex.unlock(); // 忘写或中间 return 就死锁 } // 方式2: lock_guard (推荐,作用域结束自动解锁) void f2() { std::lock_guard<std::mutex> guard(g_mutex); // ... 临界区 } // 这里自动 unlock // 方式3: unique_lock (需要手动解锁/延迟加锁时) void f3() { std::unique_lock<std::mutex> lock(g_mutex); // ... 临界区 lock.unlock(); // ... 不需要锁的部分 } 条件变量:生产者-消费者 #include <mutex> #include <condition_variable> #include <queue> std::mutex mtx; std::condition_variable cv; std::queue<int> q; // 生产者 void producer() { for (int i = 0; i < 10; ++i) { std::lock_guard<std::mutex> lock(mtx); q.push(i); cv.notify_one(); // 通知一个等待的消费者 } } // 消费者 void consumer() { while (true) { std::unique_lock<std::mutex> lock(mtx); // wait 会: 释放锁等待 → 被唤醒 → 重新加锁 cv.wait(lock, []{ return !q.empty(); }); int val = q.front(); q.pop(); lock.unlock(); // 处理 val... } } 原子操作 #include <atomic> std::atomic<int> counter{0}; // counter++ 是原子操作,无需加锁 // 适用场景: 计数器、标志位、自旋 // 对比: // 普通 int ++ → 需 mutex // atomic ++ → 无锁,更快 死锁 死锁四个必要条件: 1. 互斥 资源只能一个线程占用 2. 持有并等待 拿着一个锁还想要另一个 3. 不可剥夺 别人不能抢走已持有的锁 4. 循环等待 两个线程彼此持有对方要的锁 例子: 线程A持锁1等锁2 + 线程B持锁2等锁1 → 死锁 避免: 全局加锁顺序一致 / 用 std::lock 一次锁多个 常见坑 问题 原因 解决 死锁 加锁顺序不一 / 忘解锁 固定顺序 / std::lock / RAII 假唤醒 条件变量被无故唤醒 wait 用谓词条件,循环检查 性能差 锁粒度太粗 缩小临界区 / 用原子 数据竞争 忘记加锁 一律 RAII 锁,变量尽量用 atomic 忙等耗 CPU 自旋锁滥用 用条件变量阻塞等待 顿悟 锁保护的是「数据」,不是「代码」:所有访问同一个共享变量的地方都要用同一把锁,漏一处就出 bug。 RAII 是防呆设计:lock_guard 把"忘解锁"从可能性变成不可能,这是 C++ 现代写法核心。 原子 ≠ 万能:只能处理单个变量的简单操作;涉及多个变量的一致性仍需锁。 条件变量的 wait 里一定有循环:防止假唤醒,这是教科书写法,别省。 自测清单 理解数据竞争为什么会丢更新 会用 std::mutex + std::lock_guard 区分 lock_guard 与 unique_lock 的适用场景 能写生产者-消费者(条件变量) 理解死锁四条件,知道如何避免 知道什么时候用 std::atomic
豫公网安备41108202000982号