C++ 并发编程详解

3 阅读13分钟

C++ 并发编程详解 —— 由浅入深

一、从问题出发:为什么需要并发?

1.1 一个直观的例子

想象你在经营一家餐厅:

单线程(一个服务员):
┌────────────────────────────────────────┐
│ 服务员小王的一天:                      │
│                                         │
│ 1. 带客人入座 (5 分钟)                   │
│ 2. 等客人点菜 (10 分钟) ← 等待中...     │
│ 3. 下单给厨房 (2 分钟)                   │
│ 4. 等菜做好 (30 分钟) ← 等待中...       │
│ 5. 上菜 (3 分钟)                         │
│ 6. 等客人吃完 (20 分钟) ← 等待中...     │
│ 7. 结账 (5 分钟)                         │
│                                         │
│ 总计:75 分钟,只服务了一桌客人!        │
└────────────────────────────────────────┘

多线程(多个服务员):
┌────────────────────────────────────────┐
│ 服务员小王 + 小李 + 小张:               │
│                                         │
│ 小王:带客人入座 → 下单 → 上菜 → 结账   │
│ 小李:在 A 桌等待点菜时,去服务 B 桌     │
│ 小张:在等菜做好时,去服务 C 桌         │
│                                         │
│ 同一时间服务多桌客人,效率翻倍!         │
└────────────────────────────────────────┘

1.2 程序中的例子

// 单线程:顺序执行
void single_thread() {
    // 任务 1: 下载文件 (耗时 10 秒)
    download_file("file1.zip");  // 等待 10 秒...
    
    // 任务 2: 处理数据 (耗时 5 秒)
    process_data();  // 等待 5 秒...
    
    // 任务 3: 更新界面 (耗时 1 秒)
    update_ui();  // 等待 1 秒...
    
    // 总计:16 秒,界面 16 秒无响应!
}

// 多线程:并发执行
void multi_thread() {
    // 启动下载线程
    std::thread t1(download_file, "file1.zip");
    
    // 主线程继续响应用户操作
    while (downloading) {
        handle_user_input();  // 界面仍然流畅
    }
    
    t1.join();  // 等待下载完成
}

二、核心概念:什么是线程?

2.1 进程 vs 线程

进程 (Process):
┌─────────────────────────────────┐
│         进程 A                   │
│  ┌─────────────────────────┐   │
│  │   内存空间 (代码、数据)  │   │
│  │   ┌───┐ ┌───┐ ┌───┐    │   │
│  │   │线程 1│ │线程 2│ │线程 3│   │   │
│  │   └───┘ └───┘ └───┘    │   │
│  │      ↑     ↑     ↑      │   │
│  │      共享同一块内存      │   │
│  └─────────────────────────┘   │
└─────────────────────────────────┘

线程 (Thread):
- 进程中的一个执行单元
- 同一进程的线程共享内存
- 每个线程有自己的栈和寄存器

2.2 线程的内存模型

┌──────────────────────────────────────────┐
│              进程内存空间                 │
│                                          │
│  ┌────────────────────────────────────┐ │
│  │           代码段                   │ │  ← 所有线程共享
│  ├────────────────────────────────────┤ │
│  │           数据段                   │ │  ← 所有线程共享
│  ├────────────────────────────────────┤ │
│  │           堆 (全局变量)            │ │  ← 所有线程共享
│  ├────────────────────────────────────┤ │
│  │                                    │ │
│  │  ┌─────────┐  ┌─────────┐         │ │
│  │  │ 线程 1 栈 │  │ 线程 2 栈 │  ...   │ │  ← 每个线程独立
│  │  └─────────┘  └─────────┘         │ │
│  │     局部变量     局部变量          │ │
│  └────────────────────────────────────┘ │
└──────────────────────────────────────────┘

关键:
- 堆上的数据:线程共享 → 需要同步
- 栈上的数据:线程独立 → 无需同步

三、为什么需要锁?—— 数据竞争问题

3.1 一个直观的例子

想象银行取款:

账户余额:1000 元

线程 A (ATM 取款)          线程 B (柜台取款)
     ↓                          ↓
1. 读取余额:1000              1. 读取余额:1000
     ↓                          ↓
2. 计算:1000 - 100 = 900      2. 计算:1000 - 200 = 800
     ↓                          ↓
3. 写入余额:900               3. 写入余额:800
     ↓                          ↓
最终余额:800 元 ← 错了!应该是 700 元!

3.2 代码示例:数据竞争

#include <iostream>
#include <thread>
#include <vector>

int balance = 1000;  // 共享数据

void withdraw(int amount) {
    // 步骤 1: 读取余额
    int current = balance;
    
    // 步骤 2: 计算新余额
    current -= amount;
    
    // 步骤 3: 写入余额
    balance = current;
}

int main() {
    std::thread t1(withdraw, 100);  // ATM 取 100
    std::thread t2(withdraw, 200);  // 柜台取 200
    
    t1.join();
    t2.join();
    
    std::cout << "最终余额:" << balance << std::endl;
    // 可能是 800 或 900,但绝不是 700!
    
    return 0;
}

3.3 问题根源:竞态条件

时间线分析:

时刻    线程 A              线程 B           balance
----------------------------------------------------
T1      读取 balance=1000                   1000
T2                        读取 balance=1000  1000
T3      计算 1000-100=900                   1000
T4                        计算 1000-200=800  1000
T5      写入 balance=900                    900
T6                        写入 balance=800  800  ← 覆盖了 A 的结果!

这就是数据竞争 (Data Race)!

四、锁:解决数据竞争

4.1 互斥锁 (mutex) 的工作原理

互斥锁就像卫生间的锁:

┌────────────────────────────────────────┐
│          卫生间 (共享资源)              │
│              ┌─────┐                   │
│              │ 锁  │                   │
│              └─────┘                   │
└────────────────────────────────────────┘

线程 A:                          线程 B:
  ↓                              ↓
1. 尝试锁门                     1. 尝试锁门
   ↓                              ↓
2. 成功!进入使用               2. 失败!门外等待
   ↓                              ↓
3. 使用完毕                     3. 继续等待...
   ↓                              ↓
4. 解锁开门                     4. 成功!进入使用
                                 ↓
                              5. 使用完毕
                                 ↓
                              6. 解锁开门

4.2 代码示例:用锁保护共享数据

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

int balance = 1000;           // 共享数据
std::mutex mtx;               // 互斥锁

void withdraw(int amount) {
    mtx.lock();               // 1. 加锁
    
    // 临界区:同一时间只有一个线程能执行
    int current = balance;    // 2. 读取
    current -= amount;        // 3. 计算
    balance = current;        // 4. 写入
    
    mtx.unlock();             // 5. 解锁
}

int main() {
    std::thread t1(withdraw, 100);
    std::thread t2(withdraw, 200);
    
    t1.join();
    t2.join();
    
    std::cout << "最终余额:" << balance << std::endl;
    // 现在一定是 700!
    
    return 0;
}

4.3 更好的方式:lock_guard (RAII)

#include <mutex>

std::mutex mtx;
int balance = 1000;

void withdraw_safe(int amount) {
    // lock_guard 在构造时加锁,析构时解锁
    std::lock_guard<std::mutex> lock(mtx);
    
    // 临界区
    balance -= amount;
    
    // 函数返回时,lock_guard 析构,自动解锁
    // 即使抛出异常也会解锁!
}

// 对比:手动锁的问题
void withdraw_unsafe(int amount) {
    mtx.lock();
    balance -= amount;
    
    if (balance < 0) {
        mtx.unlock();      // 可能忘记解锁!
        throw std::runtime_error("余额不足");
    }
    
    mtx.unlock();          // 可能忘记解锁!
}

4.4 为什么用 RAII?

场景:临界区中可能抛出异常

不用 RAII (危险):
void dangerous() {
    mtx.lock();
    
    // 步骤 1
    do_something();
    
    // 步骤 2: 可能抛出异常!
    might_throw();  // ← 异常!下面的 unlock 不会执行
    
    // 步骤 3
    do_more();
    
    mtx.unlock();  // ← 不会执行!死锁!
}

使用 RAII (安全):
void safe() {
    std::lock_guard<std::mutex> lock(mtx);  // 构造时加锁
    
    do_something();
    might_throw();  // ← 即使异常
    do_more();
    
}  // ← 离开作用域,lock_guard 析构,自动解锁

五、条件变量:线程间通信

5.1 为什么需要条件变量?

场景:生产者 - 消费者问题

┌─────────────┐         ┌─────────────┐
│  生产者线程  │  ────→  │   缓冲区     │  ────→  │  消费者线程  │
│  生产数据   │  放入   │  (队列)     │  取出   │  消费数据   │
└─────────────┘         └─────────────┘         └─────────────┘

问题:
1. 缓冲区空时,消费者怎么办? → 等待
2. 缓冲区满时,生产者怎么办? → 等待
3. 如何通知对方? → 条件变量

5.2 条件变量的工作原理

条件变量就像"叫醒服务":

消费者线程:                    生产者线程:
    ↓                              ↓
1. 锁住缓冲区                   1. 锁住缓冲区
    ↓                              ↓
2. 检查:有数据吗?              2. 生产数据
    ↓                              ↓
3. 没有!等待条件变量  ←────────  3. 放入数据
   (释放锁,进入等待)               ↓
    ↓ (睡眠中...)                 4. 通知条件变量
    ↓                         ┌───→ (唤醒等待者)
    ↓                         │      ↓
    ↓ (被唤醒) ←──────────────┘   5. 解锁
    ↓
4. 重新获取锁
    ↓
5. 再次检查:有数据吗?
    ↓
6. 有!取走数据
    ↓
7. 解锁

5.3 代码示例:生产者 - 消费者

#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <iostream>

std::queue<int> buffer;      // 共享缓冲区
std::mutex mtx;              // 互斥锁
std::condition_variable cv;  // 条件变量
const int MAX_SIZE = 10;     // 缓冲区最大容量

// 生产者
void producer(int id) {
    for (int i = 0; i < 20; i++) {
        std::unique_lock<std::mutex> lock(mtx);
        
        // 等待:缓冲区不满
        cv.wait(lock, [] { return buffer.size() < MAX_SIZE; });
        
        // 生产数据
        buffer.push(i);
        std::cout << "生产者" << id << "生产:" << i << std::endl;
        
        // 通知消费者
        cv.notify_one();
    }
}

// 消费者
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        
        // 等待:缓冲区不空
        cv.wait(lock, [] { return !buffer.empty(); });
        
        // 消费数据
        int data = buffer.front();
        buffer.pop();
        std::cout << "消费者消费:" << data << std::endl;
        
        // 通知生产者
        cv.notify_one();
        
        // 退出条件
        if (data == 19) break;
    }
}

int main() {
    std::thread p1(producer, 1);
    std::thread p2(producer, 2);
    std::thread c(consumer);
    
    p1.join();
    p2.join();
    c.join();
    
    return 0;
}

5.4 为什么 wait 要在循环里?

// ❌ 错误:用 if 检查
void consumer_wrong() {
    std::unique_lock<std::mutex> lock(mtx);
    
    if (buffer.empty()) {           // ← 问题在这里!
        cv.wait(lock);              // 被唤醒后直接继续
    }
    
    // 可能有多个消费者,数据已被别人取走!
    int data = buffer.front();      // ← 可能崩溃!
}

// ✅ 正确:用 while 检查
void consumer_correct() {
    std::unique_lock<std::mutex> lock(mtx);
    
    while (buffer.empty()) {        // ← 循环检查
        cv.wait(lock);
    }
    
    // 确保真的有数据
    int data = buffer.front();      // ← 安全
}

// 或者使用带谓词的 wait (推荐)
cv.wait(lock, [] { return !buffer.empty(); });

5.5 虚假唤醒 (Spurious Wakeup)

什么是虚假唤醒?

线程 A 等待条件变量
    ↓
操作系统:唤醒线程 A (但没有通知!)
    ↓
线程 A:从 wait 返回
    ↓
如果不用循环检查 → 错误执行!

为什么会有虚假唤醒?
- 操作系统优化
- 多处理器同步问题
- 信号中断

解决方案:永远用循环检查条件!

六、原子操作:无锁编程

6.1 为什么需要原子操作?

普通操作的问题:

counter++ 看起来是一条语句,实际是三步:

1. 读取 counter 的值
2. 加 1
3. 写回 counter

多线程同时执行:

线程 A                  线程 B
  ↓                      ↓
读取 counter=0          读取 counter=0
  ↓                      ↓
计算 0+1=1              计算 0+1=1
  ↓                      ↓
写入 counter=1          写入 counter=1
  ↓                      ↓
结果:counter=1  ← 错了!应该是 2

6.2 原子操作保证

原子操作:不可分割的操作

counter++ (原子版本):

线程 A                  线程 B
  ↓                      ↓
[读取 - 加 1- 写回]       等待...
  ↓                      ↓
counter=1               [读取 - 加 1- 写回]
                         ↓
                      counter=2  ← 正确!

硬件保证:原子操作执行时,其他线程无法打断

6.3 代码示例:atomic

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

// 普通变量 (不安全)
int normal_counter = 0;

// 原子变量 (安全)
std::atomic<int> atomic_counter(0);

void increment_normal() {
    for (int i = 0; i < 10000; i++) {
        normal_counter++;  // 数据竞争!
    }
}

void increment_atomic() {
    for (int i = 0; i < 10000; i++) {
        atomic_counter++;  // 安全!
    }
}

int main() {
    std::vector<std::thread> threads;
    
    // 测试普通变量
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(increment_normal);
    }
    for (auto& t : threads) t.join();
    std::cout << "普通变量:" << normal_counter << std::endl;
    // 可能是 50000-100000 之间的任意值
    
    threads.clear();
    
    // 测试原子变量
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(increment_atomic);
    }
    for (auto& t : threads) t.join();
    std::cout << "原子变量:" << atomic_counter << std::endl;
    // 一定是 100000!
    
    return 0;
}

6.4 原子操作 vs 锁

特性原子操作互斥锁
粒度单个变量代码块
性能高 (无锁)中 (有开销)
复杂度
适用场景计数器、标志位复杂数据结构
死锁风险
// 适合原子操作的场景
std::atomic<bool> running(true);      // 标志位
std::atomic<int> counter(0);          // 计数器
std::atomic<void*> ptr(nullptr);      // 指针

// 适合锁的场景
std::map<int, std::string> data;      // 复杂数据结构
std::vector<int> queue;               // 需要多个操作的容器

七、线程池:为什么需要?

7.1 线程创建的成本

创建线程的成本:

1. 系统调用开销
   └─> 进入内核态

2. 内存分配
   └─> 栈空间 (默认 1-8MB)

3. 内核数据结构
   └─> task_struct 等

4. 上下文切换
   └─> 保存/恢复寄存器

频繁创建销毁线程 = 浪费资源!

7.2 线程池的思想

不用线程池:
任务 1 → 创建线程 → 执行 → 销毁线程
任务 2 → 创建线程 → 执行 → 销毁线程
任务 3 → 创建线程 → 执行 → 销毁线程
...
成本高!

使用线程池:
┌─────────────────────────────────────┐
│            线程池                    │
│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐   │
│  │线程 1│ │线程 2│ │线程 3│ │线程 4│   │  ← 预先创建
│  └─────┘ └─────┘ └─────┘ └─────┘   │
│       ↑       ↑       ↑       ↑     │
│       └───────┴───────┴───────┘     │
│              任务队列                │
└─────────────────────────────────────┘

任务 1,2,3,4... → 放入任务队列 → 空闲线程取走执行
线程复用,成本低!

7.3 简单线程池实现

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <vector>

class ThreadPool {
public:
    ThreadPool(size_t num_threads) {
        // 创建指定数量的工作线程
        for (size_t i = 0; i < num_threads; ++i) {
            workers_.emplace_back([this] {
                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();
                }
            });
        }
    }
    
    // 提交任务
    template<class F>
    void submit(F&& f) {
        {
            std::unique_lock<std::mutex> lock(queue_mutex_);
            tasks_.emplace(std::forward<F>(f));
        }
        condition_.notify_one();
    }
    
    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex_);
            stop_ = true;
        }
        condition_.notify_all();
        
        for (std::thread& worker : workers_) {
            worker.join();
        }
    }
    
private:
    std::vector<std::thread> workers_;           // 工作线程
    std::queue<std::function<void()>> tasks_;    // 任务队列
    std::mutex queue_mutex_;                     // 队列锁
    std::condition_variable condition_;          // 条件变量
    bool stop_ = false;                          // 停止标志
};

// 使用示例
int main() {
    ThreadPool pool(4);  // 4 个工作线程
    
    // 提交 100 个任务
    for (int i = 0; i < 100; i++) {
        pool.submit([i] {
            std::cout << "任务 " << i << " 执行中..." << std::endl;
        });
    }
    
    // 等待所有任务完成 (实际使用需要更完善的等待机制)
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    return 0;
}

八、内存模型:为什么需要?

8.1 指令重排序问题

代码:
bool ready = false;    // 1. 准备标志
int data = 0;

// 线程 1: 生产
data = 42;             // 2. 写入数据
ready = true;          // 3. 设置标志

// 线程 2: 消费
while (!ready);        // 4. 等待标志
use(data);             // 5. 使用数据

问题:编译器/CPU 可能重排序!

线程 1 实际执行顺序可能是:
ready = true;          // 先设置标志
data = 42;             // 后写入数据

线程 2 看到:
ready = true  ← 但 data 还没写入!
use(data)     ← 读到旧值!

8.2 内存序 (Memory Order)

#include <atomic>

std::atomic<int> data(0);
std::atomic<bool> ready(false);

// 线程 1: 生产
void producer() {
    data.store(42, std::memory_order_relaxed);  // 宽松序
    ready.store(true, std::memory_order_release);  // 释放序
}

// 线程 2: 消费
void consumer() {
    while (!ready.load(std::memory_order_acquire));  // 获取序
    // 保证能看到 data = 42
    use(data.load(std::memory_order_relaxed));
}

8.3 内存序类型

memory_order_relaxed (宽松序):
- 只保证原子性
- 不保证顺序
- 性能最高
- 适用:计数器

memory_order_acquire (获取序):
- 读操作
- 保证之后的读写不会被重排到前面
- 适用:获取锁、读取标志

memory_order_release (释放序):
- 写操作
- 保证之前的读写不会被重排到后面
- 适用:释放锁、设置标志

memory_order_acq_rel (获取 - 释放序):
- 读 + 写
- acquire + release
- 适用:原子交换

memory_order_seq_cst (顺序一致序):
- 最严格
- 所有线程看到相同顺序
- 默认选项
- 性能最低

九、死锁:如何避免

9.1 死锁的四个必要条件

死锁产生需要同时满足四个条件:

1. 互斥条件
   └─> 资源一次只能被一个线程占用

2. 请求与保持
   └─> 线程持有资源的同时请求其他资源

3. 不剥夺
   └─> 已获得的资源不能被强制剥夺

4. 循环等待
   └─> 形成等待环:A→B→C→A

打破任一条件即可避免死锁!

9.2 死锁示例

std::mutex mtx1, mtx2;

// 线程 1
void thread1() {
    mtx1.lock();              // 1. 锁 mtx1
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    mtx2.lock();              // 2. 锁 mtx2 (可能等待)
    // 临界区
    mtx2.unlock();
    mtx1.unlock();
}

// 线程 2
void thread2() {
    mtx2.lock();              // 1. 锁 mtx2
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    mtx1.lock();              // 2. 锁 mtx1 (可能等待)
    // 临界区
    mtx1.unlock();
    mtx2.unlock();
}

// 死锁场景:
// 线程 1: 持有 mtx1,等待 mtx2
// 线程 2: 持有 mtx2,等待 mtx1
// 互相等待,永不执行!

9.3 避免死锁的方法

// 方法 1: 固定顺序加锁 (打破循环等待)
void safe_lock() {
    // 总是先锁 mtx1,再锁 mtx2
    mtx1.lock();
    mtx2.lock();
    // ...
    mtx2.unlock();
    mtx1.unlock();
}

// 方法 2: 使用 std::lock (同时加锁)
void safer_lock() {
    std::lock(mtx1, mtx2);  // 同时锁两个,避免死锁
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    // ...
}

// 方法 3: 使用 std::scoped_lock (C++17)
void safest_lock() {
    std::scoped_lock lock(mtx1, mtx2);  // 最简单!
    // ...
}

// 方法 4: 尝试加锁 (带超时)
bool try_lock() {
    if (mtx1.try_lock_for(std::chrono::milliseconds(100))) {
        if (mtx2.try_lock_for(std::chrono::milliseconds(100))) {
            // 成功
            mtx2.unlock();
            mtx1.unlock();
            return true;
        }
        mtx1.unlock();
    }
    return false;  // 失败,稍后重试
}

十、总结:并发编程核心思想

10.1 为什么这么设计?

1. 为什么需要锁?
   └─> 保护共享数据,避免数据竞争

2. 为什么用 RAII 管理锁?
   └─> 自动释放,避免死锁,异常安全

3. 为什么需要条件变量?
   └─> 线程间通信,避免忙等待

4. 为什么用原子操作?
   └─> 无锁编程,更高性能

5. 为什么需要线程池?
   └─> 减少线程创建开销,复用线程

6. 为什么需要内存序?
   └─> 控制指令重排,保证正确性

7. 为什么会有死锁?
   └─> 多个锁的循环等待

10.2 最佳实践

// 1. 优先使用高级抽象
std::async([] { /* 任务 */ });     // 比 thread 更简单
std::atomic<int> counter;          // 比 mutex 更高效
std::scoped_lock lock(mtx1, mtx2); // 比手动 lock 更安全

// 2. 减少共享数据
void thread_func() {
    int local = 0;  // 栈上数据,无需同步
    // 只在线程结束时返回结果
    return local;
}

// 3. 明确所有权
class ThreadSafeQueue {
    std::queue<int> queue_;      // 私有数据
    mutable std::mutex mtx_;     // 保护数据
public:
    void push(int val) {
        std::scoped_lock lock(mtx_);
        queue_.push(val);
    }
};

// 4. 避免在锁内做耗时操作
void bad() {
    std::lock_guard<std::mutex> lock(mtx_);
    slow_operation();  // 锁住太久!
}

void good() {
    int data;
    {
        std::lock_guard<std::mutex> lock(mtx_);
        data = quick_copy();  // 只复制数据
    }
    slow_operation(data);  // 在锁外执行
}

10.3 工具选择指南

场景                          推荐工具
─────────────────────────────────────────
计数器/标志位                 std::atomic
保护共享数据结构              std::mutex + std::lock_guard
线程间等待通知                std::condition_variable
生产者 - 消费者                std::queue + mutex + condition_variable
一次性初始化                 std::call_once
异步任务                     std::async + std::future
大量短任务                   线程池
读多写少                     std::shared_mutex

十一、快速参考

// 包含头文件
#include <thread>           // 线程
#include <mutex>            // 互斥锁
#include <condition_variable> // 条件变量
#include <atomic>           // 原子操作
#include <future>           // 异步任务

// 创建线程就是
std::thread t(func, arg1, arg2);
t.join();   // 等待完成
t.detach(); // 分离线程

// 互斥锁
std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
std::unique_lock<std::mutex> ulock(mtx);

// 条件变量
std::condition_variable cv;
cv.wait(lock, [] { return condition; });
cv.notify_one();
cv.notify_all();

// 原子操作
std::atomic<int> counter(0);
counter++;
counter.load();
counter.store(10);

// 异步任务
auto future = std::async(std::launch::async, func);
auto result = future.get();