多线程编程

多线程是提升程序性能的核心手段(充分利用多核 CPU),但也带来了线程安全、同步互斥等问题。C++11 引入了标准线程库(<thread>),统一了多线程编程接口,替代了平台相关的 pthread(Linux)、CreateThread(Windows)。

1. std::thread

(1)线程创建与启动

std::thread 是线程类,创建对象时指定线程函数,自动启动线程。核心注意事项:

  • 线程函数可以是函数指针、函数对象、lambda 表达式;
  • 线程启动后必须调用 join()(等待线程结束)或 detach()(分离线程,让线程后台运行),否则程序退出时会调用 std::terminate() 终止;
  • 传递参数时,默认是按值拷贝,若需传递引用,需用 std::ref() 包装。
#include <thread>
#include <string>
#include <iostream>
using namespace std;

// 线程函数1:普通函数
void ThreadFunc1(int a, string msg) {
    cout << "ThreadFunc1:a=" << a << ",msg=" << msg << endl;
}

// 线程函数2:函数对象
class ThreadObj {
public:
    void operator()(int a) {
        cout << "ThreadObj:a=" << a << endl;
    }
};

int main() {
    // 1. 用普通函数创建线程
    thread t1(ThreadFunc1, 10, "hello");

    // 2. 用函数对象创建线程
    ThreadObj obj;
    thread t2(obj, 20);

    // 3. 用 lambda 创建线程(最常用)
    thread t3([](int a, string msg) {
        cout << "lambda 线程:a=" << a << ",msg=" << msg << endl;
    }, 30, "world");

    // 等待线程结束(必须调用 join() 或 detach())
    t1.join();
    t2.join();
    t3.join();

    cout << "主线程结束" << endl;
    return 0;
}

(2)线程属性

  • 线程 ID:用 this_thread::get_id() 获取当前线程 ID;
  • 线程休眠:用 this_thread::sleep_for(duration) 让线程休眠(如 sleep_for(chrono::seconds(1)) 休眠 1 秒);
  • 线程交换:用 swap(t1, t2) 或 t1.swap(t2) 交换两个线程的资源。
#include <thread>
#include <chrono>
#include <iostream>
using namespace std;

void SleepFunc() {
    cout << "线程 " << this_thread::get_id() << " 开始休眠" << endl;
    this_thread::sleep_for(chrono::milliseconds(500)); // 休眠 500ms
    cout << "线程 " << this_thread::get_id() << " 休眠结束" << endl;
}

int main() {
    thread t(SleepFunc);
    cout << "线程 t 的 ID:" << t.get_id() << endl;
    t.join();
    return 0;
}

2. 线程同步与互斥

多线程访问共享资源时,会出现“竞态条件”(Race Condition),导致数据错误。线程同步的核心是“保证共享资源的原子操作”,C++ 提供了 mutex、lock_guard、unique_lock 等工具。

(1)std::mutex

std::mutex 是最基础的互斥锁,提供 lock()(加锁)和 unlock()(解锁)接口,核心规则:

  • 同一时间只有一个线程能成功加锁,其他线程会阻塞直到锁被释放;
  • 必须保证 lock() 和 unlock() 成对出现(避免死锁或资源泄漏);
  • 推荐用 lock_guard 或 unique_lock 自动管理锁(RAII 原则)。
#include <thread>
#include <mutex>
#include <iostream>
using namespace std;

int g_count = 0; // 共享资源
mutex g_mutex;   // 互斥锁

// 线程函数:对共享资源进行累加
void Increment() {
    for (int i = 0; i < 100000; ++i) {
        // 方式1:手动加锁解锁(不推荐,容易遗漏 unlock())
        // g_mutex.lock();
        // ++g_count;
        // g_mutex.unlock();

        // 方式2:lock_guard(RAII,自动加锁解锁,推荐)
        lock_guard<mutex> lock(g_mutex);
        ++g_count; // 临界区:原子操作
    }
}

int main() {
    thread t1(Increment);
    thread t2(Increment);

    t1.join();
    t2.join();

    cout << "最终计数:" << g_count << endl; // 正确输出 200000(无竞态条件)
    return 0;
}

(2)unique_lock

lock_guard 是简单锁管理(构造加锁,析构解锁),unique_lock 更灵活,支持:

  • 延迟加锁(std::defer_lock);
  • 尝试加锁(try_lock());
  • 超时加锁(try_lock_for());
  • 手动解锁(unlock())。
#include <thread>
#include <mutex>
#include <chrono>
#include <iostream>
using namespace std;

mutex g_mutex;

void TryLockFunc() {
    unique_lock<mutex> lock(g_mutex, defer_lock); // 延迟加锁
    if (lock.try_lock_for(chrono::milliseconds(100))) { // 超时 100ms 尝试加锁
        cout << "线程 " << this_thread::get_id() << " 加锁成功" << endl;
        this_thread::sleep_for(chrono::milliseconds(200)); // 持有锁 200ms
        // 析构时自动解锁
    } else {
        cout << "线程 " << this_thread::get_id() << " 加锁失败(超时)" << endl;
    }
}

int main() {
    thread t1(TryLockFunc);
    thread t2(TryLockFunc);

    t1.join();
    t2.join();
    return 0;
}

(3)条件变量(std::condition_variable)

条件变量用于线程间通信,让一个线程等待某个条件满足后再继续执行(如生产者-消费者模型)。核心接口:

  • wait(lock):释放锁,阻塞线程,直到被 notify_one() 或 notify_all() 唤醒;
  • notify_one():唤醒一个等待的线程;
  • notify_all():唤醒所有等待的线程。
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <iostream>
using namespace std;

const int MAX_QUEUE_SIZE = 5;
queue<int> g_queue;       // 共享队列(生产者生产,消费者消费)
mutex g_mutex;            // 互斥锁
condition_variable g_cv;  // 条件变量

// 生产者线程:生产数据放入队列
void Producer() {
    for (int i = 1; i <= 10; ++i) {
        unique_lock<mutex> lock(g_mutex);

        // 等待队列不满(避免队列溢出)
        g_cv.wait(lock, []() { return g_queue.size() < MAX_QUEUE_SIZE; });

        // 生产数据
        g_queue.push(i);
        cout << "生产者生产:" << i << ",队列大小:" << g_queue.size() << endl;

        lock.unlock();
        g_cv.notify_one(); // 唤醒一个消费者

        this_thread::sleep_for(chrono::milliseconds(100)); // 模拟生产耗时
    }
}

// 消费者线程:从队列获取数据消费
void Consumer() {
    while (true) {
        unique_lock<mutex> lock(g_mutex);

        // 等待队列不为空(避免消费空队列)
        g_cv.wait(lock, []() { return !g_queue.empty(); });

        // 消费数据
        int data = g_queue.front();
        g_queue.pop();
        cout << "消费者消费:" << data << ",队列大小:" << g_queue.size() << endl;

        lock.unlock();
        g_cv.notify_one(); // 唤醒一个生产者

        if (data == 10) break; // 消费完最后一个数据退出
        this_thread::sleep_for(chrono::milliseconds(200)); // 模拟消费耗时
    }
}

int main() {
    thread producer(Producer);
    thread consumer(Consumer);

    producer.join();
    consumer.join();
    return 0;
}

(4)原子操作(std::atomic)

对于简单的共享变量(如计数器),使用 atomic 比 mutex 更高效(无需加锁,直接操作内存)。atomic 支持原子的增、减、赋值等操作,线程安全。

#include <thread>
#include <atomic>
#include <iostream>
using namespace std;

atomic<int> g_count = 0; // 原子变量(线程安全)

void Increment() {
    for (int i = 0; i < 100000; ++i) {
        ++g_count; // 原子操作,无需加锁
    }
}

int main() {
    thread t1(Increment);
    thread t2(Increment);

    t1.join();
    t2.join();

    cout << "最终计数:" << g_count << endl; // 正确输出 200000
    return 0;
}

3. 线程安全与死锁

(1)线程安全的核心原则

  • 最小临界区:只对共享资源的操作加锁,避免大面积代码阻塞;
  • 避免共享可变状态:尽量使用局部变量,或通过消息传递(而非共享内存)实现线程通信;
  • 优先原子操作:简单变量用 atomic,复杂场景用 mutex;
  • 禁止在临界区调用外部接口:外部接口可能再次加锁,导致死锁。

(2)死锁及避免

死锁是多线程编程的噩梦,指两个或多个线程互相等待对方释放锁,导致程序永久阻塞。死锁的 4 个必要条件:

  1. 互斥:资源只能被一个线程持有;
  2. 持有并等待:线程持有一个锁,同时等待另一个锁;
  3. 不可剥夺:线程持有的锁不能被强制剥夺;
  4. 循环等待:线程间形成循环等待链(如 T1 等 T2 的锁,T2 等 T1 的锁)。

死锁避免方案:

  • 按固定顺序加锁:所有线程都按相同的顺序获取多个锁(如先锁 A 再锁 B);
  • 超时加锁:用 unique_lock::try_lock_for(),超时后释放已持有锁并重试;
  • 避免持有多个锁:尽量设计为单个锁,或用 std::lock() 同时加多个锁(原子操作,避免部分加锁);
  • 减少锁的持有时间:获取锁后尽快完成操作,释放锁。

死锁示例(错误):

mutex mutexA;
mutex mutexB;

// 线程1:先锁A再锁B
void Thread1() {
    mutexA.lock();
    this_thread::sleep_for(chrono::milliseconds(100)); // 让线程2有时间锁B
    mutexB.lock(); // 死锁:等待线程2释放B
    // 业务逻辑
    mutexB.unlock();
    mutexA.unlock();
}

// 线程2:先锁B再锁A
void Thread2() {
    mutexB.lock();
    this_thread::sleep_for(chrono::milliseconds(100)); // 让线程1有时间锁A
    mutexA.lock(); // 死锁:等待线程1释放A
    // 业务逻辑
    mutexA.unlock();
    mutexB.unlock();
}

修复方案(按固定顺序加锁):

// 线程1和线程2都按“先A后B”的顺序加锁
void Thread1() {
    mutexA.lock();
    mutexB.lock();
    // 业务逻辑
    mutexB.unlock();
    mutexA.unlock();
}

void Thread2() {
    mutexA.lock();
    mutexB.lock();
    // 业务逻辑
    mutexB.unlock();
    mutexA.unlock();
}

4. 高级多线程技术

(1)线程池

线程池是“预先创建多个线程,复用线程处理任务”的技术,避免频繁创建/销毁线程的开销(线程创建销毁是重量级操作)。核心组件:

  • 任务队列:存储待执行的任务;
  • 线程池:多个工作线程,循环从任务队列获取任务执行;
  • 管理接口:添加任务、关闭线程池。
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <vector>
#include <atomic>
#include <iostream>
using namespace std;

class ThreadPool {
public:
    // 构造函数:创建n个工作线程
    explicit ThreadPool(size_t thread_num) : stop_(false) {
        for (size_t i = 0; i < thread_num; ++i) {
            workers_.emplace_back([this]() {
                while (true) {
                    function<void()> task;
                    {
                        unique_lock<mutex> lock(mtx_);
                        // 等待任务或停止信号
                        cv_.wait(lock, [this]() { return stop_ || !tasks_.empty(); });
                        // 若停止且任务队列为空,退出线程
                        if (stop_ && tasks_.empty()) return;
                        // 获取任务
                        task = move(tasks_.front());
                        tasks_.pop();
                    }
                    task(); // 执行任务
                }
            });
        }
    }

    // 析构函数:关闭线程池
    ~ThreadPool() {
        {
            unique_lock<mutex> lock(mtx_);
            stop_ = true;
        }
        cv_.notify_all(); // 唤醒所有工作线程
        for (thread& worker : workers_) {
            worker.join(); // 等待所有线程结束
        }
    }

    // 添加任务(支持任意函数和参数)
    template <typename F, typename... Args>
    void AddTask(F&& f, Args&&... args) {
        {
            unique_lock<mutex> lock(mtx_);
            // 禁止在停止后添加任务
            if (stop_) throw runtime_error("线程池已关闭,无法添加任务");
            // 封装任务(完美转发)
            tasks_.emplace([=]() {
                forward<F>(f)(forward<Args>(args)...);
            });
        }
        cv_.notify_one(); // 唤醒一个工作线程
    }

private:
    vector<thread> workers_;       // 工作线程列表
    queue<function<void()>> tasks_; // 任务队列
    mutex mtx_;                    // 互斥锁
    condition_variable cv_;         // 条件变量
    atomic<bool> stop_;            // 停止标志(原子变量,线程安全)
};

// 测试任务:计算a + b
int Add(int a, int b) {
    this_thread::sleep_for(chrono::milliseconds(100)); // 模拟任务耗时
    return a + b;
}

int main() {
    ThreadPool pool(4); // 创建4个线程的线程池

    // 添加10个任务
    for (int i = 0; i < 10; ++i) {
        pool.AddTask([i]() {
            int result = Add(i, i * 2);
            cout << "线程 " << this_thread::get_id() << " 完成任务:" << i << " + " << i*2 << " = " << result << endl;
        });
    }

    // 等待所有任务完成(这里简单休眠,实际可通过future实现)
    this_thread::sleep_for(chrono::seconds(2));

    return 0;
}

(2)std::future 与 std::promise

future 和 promise 用于线程间数据传递(获取异步任务的返回值):

  • promise:生产者线程设置值;
  • future:消费者线程获取值(会阻塞直到值被设置)。
#include <thread>
#include <future>
#include <iostream>
using namespace std;

// 生产者线程:计算结果并设置到promise
void Calculate(promise<int> prom) {
    this_thread::sleep_for(chrono::seconds(1));
    int result = 100 + 200;
    prom.set_value(result); // 设置结果
}

int main() {
    // 创建promise和future
    promise<int> prom;
    future<int> fut = prom.get_future();

    // 启动线程,传递promise(必须移动,因为promise不可拷贝)
    thread t(Calculate, move(prom));

    // 获取结果(阻塞直到set_value被调用)
    cout << "等待计算结果..." << endl;
    int result = fut.get(); // 只能调用一次get()
    cout << "计算结果:" << result << endl;

    t.join();
    return 0;
}

(3)std::async

async 是更高层次的异步编程接口,自动创建线程(或复用线程池)执行任务,并返回 future 用于获取结果。无需手动管理线程和 promise。

#include <future>
#include <iostream>
using namespace std;

int Calculate() {
    this_thread::sleep_for(chrono::seconds(1));
    return 300 + 400;
}

int main() {
    // 启动异步任务(默认创建新线程)
    future<int> fut = async(Calculate);

    cout << "等待异步任务结果..." << endl;
    int result = fut.get();
    cout << "异步任务结果:" << result << endl;

    return 0;
}

(4)std::packaged_task

std::packaged_task 是 C++11 引入的标准库模板类,用于包装一个可调用对象(如函数、lambda 表达式、函数对象等),使其能够与 std::future 协同工作。

简单来说,它将一个任务(可调用对象)包装起来,并允许你获取该任务的执行结果,这样你可以在一个线程中执行任务,并在另一个线程中获取其结果。

一个简化的设计参考

template <typename F>
class packaged_task {
public:
    packaged_task(F&& f) : task(std::forward<F>(f)), promise(), future(promise.get_future()) {}

    void operator()() {
        try {
            // 调用原始函数,并将结果通过 promise 设置
            promise.set_value(task());
        } catch (...) {
            promise.set_exception(std::current_exception());
        }
    }

    std::future<typename std::result_of<F()>::type> get_future() {
        return std::move(future);
    }

private:
    F task; // 存储原始函数
    std::promise<typename std::result_of<F()>::type> promise;
    std::future<typename std::result_of<F()>::type> future;
};

使用demo:

#include <iostream>
#include <future>
#include <thread>
#include <chrono>

// 一个简单的函数,用于计算整数平方
int square(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return x * x;
}

int main() {
    // 创建一个 std::packaged_task,包装 square 函数
    std::packaged_task<int(int)> task(square);

    // 获取关联的 std::future 对象,用于获取任务结果
    std::future<int> result = task.get_future();

    // 将任务交给一个线程异步执行
    std::thread t(std::move(task), 5); // 传递参数5给square函数
    t.detach(); // 分离线程

    // 在主线程中继续做其他事情
    std::cout << "Doing some work in the main thread..." << std::endl;

    // 获取任务的返回值,等待任务执行完成
    std::cout << "The result of square(5) is: " << result.get() << std::endl;

    return 0;
}

性能优化

C++ 的核心优势是“高性能”,性能优化是 C++ 开发的核心竞争力。优化的核心原则是“先测量,后优化”(通过 Profiler 定位瓶颈,避免盲目优化)。

1. 代码层优化

(1)减少拷贝,多用移动

拷贝操作(尤其是大对象)会消耗大量内存和 CPU,C++11 后的移动语义(std::move)可避免不必要的拷贝。

#include <vector>
#include <string>
#include <iostream>
using namespace std;

int main() {
    vector<string> vec;
    string s = "hello world";

    // 方式1:拷贝(创建新字符串,效率低)
    vec.push_back(s);
    cout << "拷贝后 s 是否为空:" << s.empty() << endl; // false

    // 方式2:移动(转移所有权,效率高,s变为空)
    vec.push_back(move(s));
    cout << "移动后 s 是否为空:" << s.empty() << endl; // true

    return 0;
}
  • 优化场景:
    1. 函数返回大对象时,返回值会自动移动(C++11 后 NRVO 优化);
    2. 容器添加元素时,对临时对象或不再使用的对象用 move;
    3. 自定义类实现移动构造/移动赋值运算符(Rule of Five)。

(2)避免不必要的内存分配

内存分配(new/malloc)是耗时操作,频繁分配会导致内存碎片和性能下降。

  • 优化方案:
    1. 预分配内存:vector::reserve() 预分配容量,避免频繁扩容;
    2. 对象池:复用对象(如线程池、连接池),减少创建/销毁开销;
    3. 栈内存优先:小对象用栈内存(局部变量),避免堆内存分配;
    4. 避免临时对象:如用 emplace_back() 替代 push_back()(原地构造对象,避免临时对象)。
#include <vector>
#include <string>
using namespace std;

struct Person {
    string name;
    int age;
    Person(string n, int a) : name(n), age(a) {}
};

int main() {
    vector<Person> vec;
    vec.reserve(100); // 预分配100个元素的容量,避免扩容

    // emplace_back:原地构造Person,无需创建临时对象
    vec.emplace_back("Tom", 18);

    // push_back:创建临时Person,再拷贝/移动到容器
    vec.push_back(Person("Jerry", 17));

    return 0;
}

(3)循环优化

循环是性能热点,优化循环能大幅提升性能:

  • 减少循环内的计算:将不变的计算移到循环外;
  • 减少循环内的函数调用:函数调用有开销,可内联(inline)或手动展开;
  • 缓存友好:按内存顺序访问数据(CPU 缓存是按块加载的,连续访问命中率高);
  • 循环展开:减少循环控制开销(适用于循环次数固定的场景)。
// 优化前:循环内重复计算
for (int i = 0; i < vec.size(); ++i) {
    // vec.size() 每次循环都要计算(vector的size是O(1),但仍有开销)
}

// 优化后:将size移到循环外
int size = vec.size();
for (int i = 0; i < size; ++i) {
    // ...
}

// 缓存友好:连续访问数组(CPU缓存命中率高)
int arr[1000];
for (int i = 0; i < 1000; ++i) {
    arr[i] = i; // 连续访问,缓存友好
}

// 非缓存友好:跳跃访问(缓存命中率低)
for (int i = 0; i < 1000; i += 100) {
    arr[i] = i; // 跳跃访问,缓存失效频繁
}

(4)使用高效的数据结构和算法

  • 查找场景:unordered_map(平均 O(1))优于 map(O(log n)),需有序时用 map;
  • 排序场景:sort(快速排序)优于冒泡排序、插入排序;
  • 容器选择:根据操作场景选择(如频繁尾部增删用 vector,中间增删用 list);
  • 避免使用低效算法:如嵌套循环(O(n²)),尽量用 O(n) 或 O(n log n) 算法。

2. 编译优化

编译器优化是“零成本”优化,通过编译器参数开启优化,无需修改代码:

  • GCC/Clang:-O1(基础优化)、-O2(常用优化,平衡速度和编译时间)、-O3(极致优化,可能增大二进制体积);
  • MSVC:/O1(最小化体积)、/O2(最大化速度)。

编译器优化的核心内容:

  • 常量折叠:计算编译期常量(如 3+5 直接优化为 8);
  • 死代码消除:删除永远不会执行的代码;
  • 循环优化:循环展开、循环变量外提;
  • 函数内联:消除函数调用开销;
  • 指令重排:优化指令执行顺序,充分利用 CPU 流水线。

3. 内存优化

(1)减少内存碎片

内存碎片是频繁分配/释放不同大小的堆内存导致的,会浪费内存并降低分配效率:

  • 用 vector 替代多个独立的 new:vector 是连续内存,减少碎片;
  • 使用内存池:预先分配一块大内存,按需分配小块内存,释放时归还给内存池;
  • 对齐内存:alignas 指定内存对齐(CPU 访问对齐的内存更快)。
// 指定内存对齐为8字节
struct alignas(8) Data {
    int a;
    char b;
};

// 内存池示例(简化)
class MemoryPool {
private:
    char* pool_;
    size_t size_;
    size_t used_;
public:
    MemoryPool(size_t size) : size_(size), used_(0) {
        pool_ = new char[size];
    }
    ~MemoryPool() {
        delete[] pool_;
    }
    void* Allocate(size_t alloc_size) {
        if (used_ + alloc_size > size_) return nullptr;
        void* ptr = pool_ + used_;
        used_ += alloc_size;
        return ptr;
    }
    void Deallocate(void* ptr) {
        // 简化实现:不实际释放,仅重置used_(适用于批量释放场景)
        if (ptr == pool_) used_ = 0;
    }
};

(2)缓存优化

CPU 缓存是提升内存访问速度的关键(缓存速度比内存快 10-100 倍),优化缓存命中率:

  • 数据局部性:将频繁访问的数据放在一起(如结构体成员按访问频率排序);
  • 避免缓存失效:减少对同一缓存行的并发写(伪共享);
  • 预取数据:__builtin_prefetch(GCC)或 _mm_prefetch(MSVC)手动预取数据到缓存。

工程化规范与实践

工程化开发的核心是“让代码可维护、可协作、可扩展”,涵盖编码规范、版本控制、构建系统、测试等多个方面。

1. 编码规范

编码规范是团队协作的基础,统一的风格能减少沟通成本,提升代码可读性。推荐遵循:

  • Google C++ 编码规范:最流行的规范,涵盖命名、格式、注释、特性使用等;
  • ISO C++ Core Guidelines:C++ 标准委员会推荐的指南,聚焦安全性、高效性和可维护性。

核心规范要点

  • 命名:
    • 类/结构体/枚举:大驼峰(PersonInfo、LogLevel);
    • 函数/变量:小驼峰(calculateSum、userName)或下划线(calculate_sum、user_name);
    • 常量/宏:全大写+下划线(MAX_QUEUE_SIZE、LOG_DEBUG);
    • 私有成员:后缀加下划线(name_、age_)。
  • 格式:
    • 缩进:4 个空格(避免 Tab,跨编辑器一致);
    • 行宽:不超过 80-120 字符;
    • 大括号:函数/类的左大括号单独一行,循环/条件的左大括号紧跟语句。
  • 注释:
    • 类/函数:用 Doxygen 风格注释(说明功能、参数、返回值、异常);
    • 复杂逻辑:添加行注释,说明设计思路;
    • 避免冗余注释(如“i++:i自增1”)。
  • 特性使用:
    • 禁用裸 new/delete,用智能指针;
    • 禁用宏,用 constexpr 或 inline 函数;
    • 优先使用标准库,避免重复造轮子;
    • 避免使用 void*、裸数组,用 std::any、std::vector。