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

2026 年 10 月 13 日

C++ 线程基础:std::thread、join/detach 与传参陷阱

std::thread 的创建方式、join 与 detach 的语义、传参陷阱与常见坑。同步机制(互斥锁、条件变量、原子操作)见另一篇并发笔记。 核心概念速查 概念 说明 关键点 进程 独立运行的程序实例 有独立内存空间 线程 进程内的执行单元 共享进程内存 std::thread C++11 线程库 #include <thread> join 等待线程结束 阻塞当前线程 detach 分离线程 后台运行,不再可 join 进程 vs 线程 维度 进程 线程 内存空间 独立 共享 通信 管道/队列/文件 直接共享变量(需同步) 切换开销 大 小 创建开销 大 小 崩溃影响 只影响自身 可能带崩整个进程 适用场景 隔离、分布式 高并发、I/O 密集 创建线程 #include <iostream> #include <thread> void hello() { std::cout << "Hello from thread\n"; } int main() { // 方式1: 无参函数 std::thread t1(hello); // 方式2: lambda std::thread t2([]{ std::cout << "Hello from lambda\n"; }); // 方式3: 带参函数 auto print_num = [](int n) { std::cout << "Number: " << n << "\n"; }; std::thread t3(print_num, 42); t1.join(); t2.join(); t3.join(); return 0; } join 与 detach 操作 行为 何时用 join() 等待线程结束才继续 需要线程结果时 detach() 立即返回,线程后台跑 结果不关心时(慎用) 陷阱:线程要么 join 要么 detach,都不做 → 析构时 std::terminate() 崩溃。detach 的线程访问已销毁的对象 → 未定义行为。 ...

2026 年 10 月 13 日