一、并发基础核心组件

口诀:线程对象要管理,互斥锁来保安全,原子操作不可拆,条件变量通信忙

1.1 std::thread - 线程管理

  • 构造方式
    • 默认构造:创建空线程对象
    • 初始化构造:thread t(func, args...),传入函数和参数
    • 移动构造:支持线程所有权转移,不支持拷贝
  • 生命周期管理
    • join():主线程等待子线程完成,阻塞当前线程
    • detach():线程独立运行,与主线程分离
    • 关键注意:线程对象销毁前必须调用join()或detach(),否则程序崩溃
  • 常见陷阱:局部线程对象销毁时,若线程函数仍在运行会导致崩溃(解决:使用detach()或确保join()被调用)

1.2 std::mutex - 互斥锁

  • 基本功能:保护共享资源,确保同一时间只有一个线程访问
  • 核心方法lock()(加锁)、unlock()(解锁)、try_lock()(尝试加锁,非阻塞)
  • 使用建议:配合RAII包装器使用,避免忘记解锁导致死锁

1.3 std::lock_guard - RAII锁管理

  • 核心特性:RAII风格的锁管理,构造时自动加锁,析构时自动解锁
  • 优点:避免忘记解锁,防止异常导致的死锁
  • 限制:作用域内持续锁定,无法中途解锁,不可复制

1.4 std::unique_lock - 灵活锁管理

  • 核心特性:lock_guard的升级版,提供更多灵活性
  • 主要优势
    • 支持延迟锁定:unique_lock(mtx, defer_lock)
    • 可随时加锁解锁:lock(), unlock()
    • 可移动,不可复制
    • 支持条件变量(必须使用unique_lock)

二、线程创建与管理详解

口诀:线程创建传函数,join等待detach离,现代推荐jthread,自动join更安全

2.1 线程创建方法

  • 函数指针std::thread t(func, args...)
  • Lambda表达式std::thread t([]{ /* 线程代码 */ });
  • 类成员函数std::thread t(&Class::method, &obj, args...)

2.2 join() vs detach() 选择

  • join()适用场景:需要等待线程完成,依赖其执行结果
  • detach()适用场景:后台任务、日志记录、监控等无需主线程等待的情况
  • 注意:detach()后线程由运行时管理,无法获取状态或结果

2.3 现代C++线程管理

  • C++20 std::jthread:自动在析构时调用join(),避免忘记管理的风险
  • std::async:更高级的异步任务接口,自动管理线程和返回结果
    1
    2
    std::future<int> result = std::async(std::launch::async, add, 5, 3);
    int val = result.get(); // 阻塞等待结果

三、lock_guard与unique_lock对比

口诀:lock_guard自动锁,作用域内不解锁;unique_lock更灵活,条件变量配合多

特性 lock_guard unique_lock
自动锁定解锁
中途解锁
延迟锁定
移动语义
条件变量支持
适用场景 简单临界区 复杂同步场景

四、std::atomic - 原子操作

口诀:原子操作不可拆,无需锁也能同步,适用于简单计数器,性能更高更安全

  • 核心概念:确保对变量的操作是不可分割的,要么全部完成,要么完全不执行

  • 适用场景:计数器、标志位、引用计数等简单共享变量

  • 常用操作fetch_add()fetch_sub()load()store()

  • 优势:无需互斥锁,避免上下文切换开销,性能更高

  • 注意:复杂逻辑仍需互斥锁保护,atomic仅保证单个操作的原子性

    1
    2
    std::atomic<int> counter(0);
    void increment() { counter.fetch_add(1); } // 原子递增

五、多线程同步机制详解

口诀:互斥锁保临界区,条件变量通信忙,原子操作性能高,信号量控资源量

5.1 互斥锁(std::mutex)

  • 作用:保护临界区,确保同一时间只有一个线程访问共享资源
  • 使用方式:配合lock_guard或unique_lock使用
    1
    2
    3
    4
    5
    std::mutex mtx;
    void critical_section() {
    std::lock_guard<std::mutex> lock(mtx);
    // 共享资源操作
    }

5.2 条件变量(std::condition_variable)

  • 作用:线程间通信,等待特定条件满足后继续执行
  • 核心方法
    • wait(lock, predicate):等待条件,可带谓词
    • notify_one():唤醒一个等待线程
    • notify_all():唤醒所有等待线程
  • 使用示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;

    void wait_for_ready() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; }); // 等待ready变为true
    // 条件满足后的操作
    }

    void set_ready() {
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
    cv.notify_all(); // 通知所有等待线程
    }

5.3 信号量(std::counting_semaphore)

  • 作用:控制对共享资源的并发访问数量
  • 核心方法
    • acquire():获取资源,若资源不足则阻塞
    • release():释放资源,增加可用资源数量
  • 使用示例
    1
    2
    3
    4
    5
    6
    7
    std::counting_semaphore<10> sem(10); // 最多10个线程同时访问

    void access_resource() {
    sem.acquire(); // 获取许可
    // 访问共享资源
    sem.release(); // 释放许可
    }

六、线程池实现思路

口诀:任务队列来存储,工作线程池中取,互斥锁加条件量,线程管理自动化

6.1 核心组件

  1. 任务队列std::queue<std::function<void()>> 存储待执行任务
  2. 工作线程池std::vector<std::thread> 保存工作线程
  3. 同步机制
    • std::mutex:保护任务队列访问
    • std::condition_variable:通知等待的线程
    • std::atomic<bool>:线程池关闭标志

6.2 实现流程

  1. 初始化:创建指定数量的工作线程,每个线程进入循环
  2. 工作线程逻辑
    • 获取互斥锁
    • 检查任务队列是否为空,为空则等待条件变量
    • 非空则取出任务并执行
    • 循环直到收到关闭信号
  3. 任务提交:将任务添加到队列,通知等待的线程
  4. 关闭:设置关闭标志,通知所有线程,等待线程结束

6.3 核心代码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 工作线程循环的核心逻辑
void worker_thread() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex);
// 等待直到有任务或关闭信号
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
// 如果关闭且队列为空,退出线程
if (stop && tasks.empty()) return;
// 取出任务
task = std::move(tasks.front());
tasks.pop();
}
// 执行任务(解锁状态下)
task();
}
}

七、多线程编程最佳实践

口诀:避免共享数据,必须共享则同步,优先使用RAII锁,死锁预防是关键

7.1 死锁预防策略

  • 按固定顺序获取锁,避免循环等待
  • 使用std::lock(mtx1, mtx2)同时锁定多个互斥量
  • 配合lock_guardadopt_lock标签使用
  • 避免在持有锁时调用未知函数
  • 设置锁的超时机制(如使用std::timed_mutex

7.2 性能优化技巧

  • 减少锁的粒度,仅保护必要的共享数据
  • 优先使用原子操作代替互斥锁(简单场景)
  • 使用std::shared_mutex实现读写锁(读多写少场景)
  • 利用thread_local避免数据竞争
  • 根据硬件并发数调整线程数量:std::thread::hardware_concurrency()

7.3 现代C++并发工具

  • C++17 std::shared_mutex:读写锁,允许多个读线程同时访问

  • C++20 std::jthread:自动join的线程,更安全

  • C++20 std::latch / std::barrier:线程同步点控制

  • C++11 std::future / std::promise:异步任务和结果获取

  • C++17 std::shared_future:允许多个线程共享future结果

总结:C++并发编程的核心在于正确管理线程生命周期和同步共享资源。通过掌握std::thread、互斥锁、条件变量、原子操作等基础工具,结合RAII原则和现代C++特性,可以编写出高效、安全的多线程程序。重点记忆线程管理规则、锁的使用场景区别以及死锁预防策略,能够有效避免常见的并发编程陷阱。